diff --git a/src/main/java/com/ycwl/basic/order/dto/RefundRequest.java b/src/main/java/com/ycwl/basic/order/dto/RefundRequest.java index a3cb2658..a2bc4058 100644 --- a/src/main/java/com/ycwl/basic/order/dto/RefundRequest.java +++ b/src/main/java/com/ycwl/basic/order/dto/RefundRequest.java @@ -10,6 +10,11 @@ import java.math.BigDecimal; @Data public class RefundRequest { + /** + * 退款单号(幂等键,必填) + */ + private String refundNo; + /** * 订单ID */ @@ -49,4 +54,4 @@ public class RefundRequest { * 退款渠道 */ private String refundChannel = "ORIGINAL"; -} \ No newline at end of file +} diff --git a/src/main/java/com/ycwl/basic/order/service/IOrderService.java b/src/main/java/com/ycwl/basic/order/service/IOrderService.java index f41d982d..d7304267 100644 --- a/src/main/java/com/ycwl/basic/order/service/IOrderService.java +++ b/src/main/java/com/ycwl/basic/order/service/IOrderService.java @@ -102,9 +102,11 @@ public interface IOrderService { * * @param orderId 订单ID * @param refundStatus 退款状态 + * @param refundNo 退款单号(幂等键) + * @param paymentRefundId 支付平台退款单号 * @return 更新结果 */ - boolean updateRefundStatus(Long orderId, RefundStatus refundStatus); + boolean updateRefundStatus(Long orderId, RefundStatus refundStatus, String refundNo, String paymentRefundId); /** * 创建退款记录 @@ -142,4 +144,4 @@ public interface IOrderService { * @return 回调处理结果 */ PaymentCallbackResponse handlePaymentCallback(Long scenicId, HttpServletRequest request); -} \ No newline at end of file +} diff --git a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java index 3edff0f7..99ae7517 100644 --- a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java +++ b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java @@ -47,6 +47,7 @@ import com.ycwl.basic.repository.FaceRepository; import com.ycwl.basic.service.pc.ScenicService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -264,20 +265,31 @@ public class OrderServiceImpl implements IOrderService { @Override public boolean updateOrderStatus(Long orderId, String orderStatus, String paymentStatus) { + OrderV2 currentOrder = orderV2Mapper.selectById(orderId); + if (currentOrder == null) { + log.warn("订单不存在: orderId={}", orderId); + return false; + } + + OrderStatus targetOrderStatus = OrderStatus.fromCode(orderStatus); + PaymentStatus targetPaymentStatus = PaymentStatus.fromCode(paymentStatus); + validateOrderStatusTransition(currentOrder, targetOrderStatus, targetPaymentStatus); + + if (currentOrder.getOrderStatus() == targetOrderStatus + && currentOrder.getPaymentStatus() == targetPaymentStatus) { + return true; + } + OrderV2 order = new OrderV2(); order.setId(orderId); - order.setOrderStatus(OrderStatus.fromCode(orderStatus)); - order.setPaymentStatus(PaymentStatus.fromCode(paymentStatus)); + order.setOrderStatus(targetOrderStatus); + order.setPaymentStatus(targetPaymentStatus); order.setUpdateTime(new Date()); - - if ("PAID".equals(paymentStatus)) { + + if (targetPaymentStatus == PaymentStatus.PAID && currentOrder.getPayTime() == null) { order.setPayTime(new Date()); } - - if ("COMPLETED".equals(orderStatus)) { - order.setCompleteTime(new Date()); - } - + return orderV2Mapper.updateById(order) > 0; } @@ -354,74 +366,101 @@ public class OrderServiceImpl implements IOrderService { log.warn("订单不存在: orderId={}", orderId); return false; } - + + PaymentStatus targetPaymentStatus = PaymentStatus.fromCode(paymentStatus); + validatePaymentStatusTransition(currentOrder, targetPaymentStatus); + PaymentStatus oldPaymentStatus = currentOrder.getPaymentStatus(); - PaymentStatus newPaymentStatus = PaymentStatus.fromCode(paymentStatus); - - // 更新订单支付状态 + if (oldPaymentStatus == targetPaymentStatus) { + return true; + } + OrderV2 order = new OrderV2(); order.setId(orderId); - order.setPaymentStatus(newPaymentStatus); + order.setPaymentStatus(targetPaymentStatus); + order.setOrderStatus(OrderStatus.PAID); order.setUpdateTime(new Date()); - - if ("PAID".equals(paymentStatus)) { + + if (currentOrder.getPayTime() == null) { order.setPayTime(new Date()); - // 支付完成后,订单状态也需要更新 - order.setOrderStatus(OrderStatus.PAID); } - + boolean success = orderV2Mapper.updateById(order) > 0; - - // 发布支付状态变更事件 - if (success && !oldPaymentStatus.equals(newPaymentStatus)) { + + if (success) { PaymentStatusChangeEvent event = new PaymentStatusChangeEvent( orderId, currentOrder.getOrderNo(), - oldPaymentStatus, newPaymentStatus, + oldPaymentStatus, targetPaymentStatus, "支付状态更新", null ); orderEventManager.publishPaymentStatusChangeEvent(event); } - + return success; } @Override @Transactional - public boolean updateRefundStatus(Long orderId, RefundStatus refundStatus) { - // 先查询当前订单状态 + public boolean updateRefundStatus(Long orderId, RefundStatus refundStatus, String refundNo, String paymentRefundId) { OrderV2 currentOrder = orderV2Mapper.selectById(orderId); if (currentOrder == null) { log.warn("订单不存在: orderId={}", orderId); return false; } - - RefundStatus oldRefundStatus = currentOrder.getRefundStatus(); - - // 更新订单退款状态 - OrderV2 order = new OrderV2(); - order.setId(orderId); - order.setRefundStatus(refundStatus); - order.setUpdateTime(new Date()); - - // 根据退款状态更新订单状态 - if (RefundStatus.FULL_REFUND.equals(refundStatus)) { - order.setOrderStatus(OrderStatus.REFUNDED); - } else if (RefundStatus.REFUND_PROCESSING.equals(refundStatus)) { - order.setOrderStatus(OrderStatus.REFUNDING); + + if (refundStatus == null) { + throw new BaseException("退款状态不能为空"); } - - boolean success = orderV2Mapper.updateById(order) > 0; - - // 发布退款状态变更事件 - if (success && !oldRefundStatus.equals(refundStatus)) { + + validateRefundStatusTransition(currentOrder, refundStatus); + + OrderRefundV2 refundRecord = resolveRefundRecord(orderId, refundNo); + String resolvedRefundNo = refundRecord.getRefundNo(); + if (StringUtils.isBlank(resolvedRefundNo)) { + throw new BaseException("退款单号不能为空"); + } + + RefundStatus oldRefundStatus = currentOrder.getRefundStatus(); + boolean refundStatusChanged = oldRefundStatus != refundStatus; + boolean paymentRefundIdNeedUpdate = StringUtils.isNotBlank(paymentRefundId) + && StringUtils.isBlank(refundRecord.getPaymentRefundId()); + + if (!refundStatusChanged && !paymentRefundIdNeedUpdate + && isOrderRefundStateConsistent(currentOrder, refundStatus)) { + return true; + } + + if (refundStatusChanged || paymentRefundIdNeedUpdate) { + OrderRefundV2 updateRefund = new OrderRefundV2(); + updateRefund.setId(refundRecord.getId()); + updateRefund.setRefundStatus(refundStatus); + if (paymentRefundIdNeedUpdate) { + updateRefund.setPaymentRefundId(paymentRefundId); + } + updateRefund.setUpdateTime(new Date()); + orderRefundMapper.updateById(updateRefund); + } + + boolean needUpdateOrder = refundStatusChanged || !isOrderRefundStateConsistent(currentOrder, refundStatus); + boolean success = true; + if (needUpdateOrder) { + OrderV2 order = new OrderV2(); + order.setId(orderId); + order.setRefundStatus(refundStatus); + applyOrderRefundState(order, refundStatus); + order.setUpdateTime(new Date()); + success = orderV2Mapper.updateById(order) > 0; + } + + if (success && refundStatusChanged) { RefundStatusChangeEvent event = new RefundStatusChangeEvent( - orderId, currentOrder.getOrderNo(), null, null, - oldRefundStatus, refundStatus, null, + orderId, currentOrder.getOrderNo(), refundRecord.getId(), resolvedRefundNo, + oldRefundStatus, refundStatus, refundRecord.getRefundAmount(), "退款状态更新", null ); orderEventManager.publishRefundStatusChangeEvent(event); } - + return success; } @@ -429,18 +468,51 @@ public class OrderServiceImpl implements IOrderService { @Transactional public Long createRefundRecord(RefundRequest request) { Date now = new Date(); - - // 生成退款单号 - String refundNo = generateRefundNo(); - - // 创建退款记录 + + if (request == null) { + throw new BaseException("退款请求不能为空"); + } + if (request.getOrderId() == null) { + throw new BaseException("订单ID不能为空"); + } + if (StringUtils.isBlank(request.getRefundNo())) { + throw new BaseException("退款单号不能为空"); + } + if (StringUtils.isBlank(request.getRefundType())) { + throw new BaseException("退款类型不能为空"); + } + + OrderV2 order = orderV2Mapper.selectById(request.getOrderId()); + if (order == null) { + throw new BaseException("订单不存在"); + } + if (order.getOrderStatus() == OrderStatus.CANCELLED || order.getPaymentStatus() == PaymentStatus.UNPAID) { + throw new BaseException("未支付或已取消订单不允许退款"); + } + if (order.getRefundStatus() == RefundStatus.FULL_REFUND) { + throw new BaseException("订单已完成退款"); + } + + String refundNo = request.getRefundNo(); + OrderRefundV2 existingRefund = findRefundByNo(refundNo); + if (existingRefund != null) { + if (!request.getOrderId().equals(existingRefund.getOrderId())) { + throw new BaseException("退款单号已存在"); + } + RefundStatus existingStatus = existingRefund.getRefundStatus() != null + ? existingRefund.getRefundStatus() + : RefundStatus.REFUND_PROCESSING; + updateRefundStatus(request.getOrderId(), existingStatus, refundNo, existingRefund.getPaymentRefundId()); + return existingRefund.getId(); + } + OrderRefundV2 refund = new OrderRefundV2(); refund.setOrderId(request.getOrderId()); refund.setRefundNo(refundNo); refund.setRefundType(RefundType.fromCode(request.getRefundType())); refund.setRefundAmount(request.getRefundAmount()); refund.setRefundFee(request.getRefundFee()); - refund.setRefundStatus(com.ycwl.basic.order.enums.RefundStatus.REFUND_PROCESSING); + refund.setRefundStatus(RefundStatus.REFUND_PROCESSING); refund.setRefundReason(request.getRefundReason()); refund.setRefundDescription(request.getRefundDescription()); refund.setOperatorRemarks(request.getOperatorRemarks()); @@ -448,15 +520,14 @@ public class OrderServiceImpl implements IOrderService { refund.setApplyTime(now); refund.setCreateTime(now); refund.setUpdateTime(now); - + orderRefundMapper.insert(refund); - - // 更新订单退款状态 - updateRefundStatus(request.getOrderId(), com.ycwl.basic.order.enums.RefundStatus.REFUND_PROCESSING); - - log.info("退款记录创建成功: refundId={}, refundNo={}, orderId={}, refundAmount={}", + + updateRefundStatus(request.getOrderId(), RefundStatus.REFUND_PROCESSING, refundNo, null); + + log.info("退款记录创建成功: refundId={}, refundNo={}, orderId={}, refundAmount={}", refund.getId(), refundNo, request.getOrderId(), request.getRefundAmount()); - + return refund.getId(); } @@ -467,6 +538,163 @@ public class OrderServiceImpl implements IOrderService { } // ====== 私有辅助方法 ====== + + private void validateOrderStatusTransition(OrderV2 currentOrder, OrderStatus targetOrderStatus, + PaymentStatus targetPaymentStatus) { + if (currentOrder == null || targetOrderStatus == null || targetPaymentStatus == null) { + throw new BaseException("订单状态参数不能为空"); + } + OrderStatus currentOrderStatus = currentOrder.getOrderStatus(); + PaymentStatus currentPaymentStatus = currentOrder.getPaymentStatus(); + if (currentOrderStatus == null || currentPaymentStatus == null) { + throw new BaseException("订单状态异常"); + } + if (currentOrderStatus == targetOrderStatus && currentPaymentStatus == targetPaymentStatus) { + return; + } + + boolean allowed = false; + if (currentOrderStatus == OrderStatus.PENDING_PAYMENT) { + allowed = (targetOrderStatus == OrderStatus.PAID && targetPaymentStatus == PaymentStatus.PAID) + || (targetOrderStatus == OrderStatus.CANCELLED && targetPaymentStatus == PaymentStatus.UNPAID); + } else if (currentOrderStatus == OrderStatus.PAID) { + allowed = (targetOrderStatus == OrderStatus.REFUNDING && targetPaymentStatus == PaymentStatus.PAID) + || (targetOrderStatus == OrderStatus.REFUNDED && targetPaymentStatus == PaymentStatus.REFUNDED); + } else if (currentOrderStatus == OrderStatus.REFUNDING) { + allowed = targetOrderStatus == OrderStatus.REFUNDED && targetPaymentStatus == PaymentStatus.REFUNDED; + } else if (currentOrderStatus == OrderStatus.CANCELLED) { + allowed = targetOrderStatus == OrderStatus.CANCELLED && targetPaymentStatus == PaymentStatus.UNPAID; + } else if (currentOrderStatus == OrderStatus.REFUNDED) { + allowed = targetOrderStatus == OrderStatus.REFUNDED && targetPaymentStatus == PaymentStatus.REFUNDED; + } + + if (!allowed) { + throw new BaseException("订单状态流转非法"); + } + } + + private void validatePaymentStatusTransition(OrderV2 currentOrder, PaymentStatus targetPaymentStatus) { + if (currentOrder == null || targetPaymentStatus == null) { + throw new BaseException("支付状态参数不能为空"); + } + if (currentOrder.getOrderStatus() == null || currentOrder.getPaymentStatus() == null) { + throw new BaseException("订单状态异常"); + } + if (targetPaymentStatus != PaymentStatus.PAID) { + throw new BaseException("支付状态仅允许更新为已支付"); + } + if (currentOrder.getOrderStatus() == OrderStatus.CANCELLED) { + throw new BaseException("已取消订单不允许支付"); + } + if (currentOrder.getPaymentStatus() == PaymentStatus.PAID) { + if (currentOrder.getOrderStatus() == OrderStatus.REFUNDED) { + throw new BaseException("订单状态不允许支付"); + } + return; + } + if (currentOrder.getPaymentStatus() != PaymentStatus.UNPAID + || currentOrder.getOrderStatus() != OrderStatus.PENDING_PAYMENT) { + throw new BaseException("订单状态不允许支付"); + } + } + + private void validateRefundStatusTransition(OrderV2 currentOrder, RefundStatus targetRefundStatus) { + if (currentOrder == null || targetRefundStatus == null) { + throw new BaseException("退款状态参数不能为空"); + } + if (currentOrder.getOrderStatus() == null || currentOrder.getPaymentStatus() == null) { + throw new BaseException("订单状态异常"); + } + if (currentOrder.getOrderStatus() == OrderStatus.CANCELLED) { + throw new BaseException("已取消订单不允许退款"); + } + if (currentOrder.getPaymentStatus() == PaymentStatus.UNPAID) { + throw new BaseException("未支付订单不允许退款"); + } + + RefundStatus currentRefundStatus = currentOrder.getRefundStatus(); + if (currentRefundStatus == null) { + throw new BaseException("订单退款状态异常"); + } + if (currentRefundStatus == targetRefundStatus) { + return; + } + + boolean allowed = false; + if (currentRefundStatus == RefundStatus.NO_REFUND) { + allowed = targetRefundStatus == RefundStatus.REFUND_PROCESSING + || targetRefundStatus == RefundStatus.PARTIAL_REFUND + || targetRefundStatus == RefundStatus.FULL_REFUND; + } else if (currentRefundStatus == RefundStatus.REFUND_PROCESSING) { + allowed = targetRefundStatus == RefundStatus.PARTIAL_REFUND + || targetRefundStatus == RefundStatus.FULL_REFUND; + } else if (currentRefundStatus == RefundStatus.PARTIAL_REFUND) { + allowed = targetRefundStatus == RefundStatus.FULL_REFUND; + } + + if (!allowed) { + throw new BaseException("退款状态流转非法"); + } + } + + private boolean isOrderRefundStateConsistent(OrderV2 order, RefundStatus refundStatus) { + if (order == null || refundStatus == null) { + return false; + } + if (refundStatus == RefundStatus.REFUND_PROCESSING || refundStatus == RefundStatus.PARTIAL_REFUND) { + return order.getOrderStatus() == OrderStatus.REFUNDING + && order.getPaymentStatus() == PaymentStatus.PAID; + } + if (refundStatus == RefundStatus.FULL_REFUND) { + return order.getOrderStatus() == OrderStatus.REFUNDED + && order.getPaymentStatus() == PaymentStatus.REFUNDED; + } + return order.getRefundStatus() == refundStatus; + } + + private void applyOrderRefundState(OrderV2 order, RefundStatus refundStatus) { + if (order == null || refundStatus == null) { + return; + } + if (refundStatus == RefundStatus.REFUND_PROCESSING || refundStatus == RefundStatus.PARTIAL_REFUND) { + order.setOrderStatus(OrderStatus.REFUNDING); + order.setPaymentStatus(PaymentStatus.PAID); + } else if (refundStatus == RefundStatus.FULL_REFUND) { + order.setOrderStatus(OrderStatus.REFUNDED); + order.setPaymentStatus(PaymentStatus.REFUNDED); + } + } + + private OrderRefundV2 resolveRefundRecord(Long orderId, String refundNo) { + if (StringUtils.isNotBlank(refundNo)) { + OrderRefundV2 refundRecord = findRefundByNo(refundNo); + if (refundRecord == null) { + throw new BaseException("退款单不存在"); + } + if (!orderId.equals(refundRecord.getOrderId())) { + throw new BaseException("退款单不存在"); + } + return refundRecord; + } + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("order_id", orderId).orderByDesc("create_time"); + List refunds = orderRefundMapper.selectList(queryWrapper); + if (refunds.isEmpty()) { + throw new BaseException("退款单不存在"); + } + log.warn("退款单号缺失,使用最新退款记录: orderId={}", orderId); + return refunds.get(0); + } + + private OrderRefundV2 findRefundByNo(String refundNo) { + if (StringUtils.isBlank(refundNo)) { + return null; + } + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("refund_no", refundNo); + return orderRefundMapper.selectOne(queryWrapper); + } /** * 构建订单查询条件 @@ -624,8 +852,9 @@ public class OrderServiceImpl implements IOrderService { } // 3. 验证订单状态 - if (order.getPaymentStatus() != PaymentStatus.UNPAID) { - throw new RuntimeException("订单状态不允许支付"); + if (order.getOrderStatus() != OrderStatus.PENDING_PAYMENT + || order.getPaymentStatus() != PaymentStatus.UNPAID) { + throw new BaseException("订单状态不允许支付"); } // 4. 获取用户openId(从订单中获取) @@ -713,31 +942,15 @@ public class OrderServiceImpl implements IOrderService { if (callbackResponse.isPay()) { // 支付成功 statusChangeType = "PAID"; - updatePaymentStatus(order.getId(), PaymentStatus.PAID.name()); - - // 触发支付成功事件 - orderEventManager.publishPaymentStatusChangeEvent( - new PaymentStatusChangeEvent(order.getId(), order.getOrderNo(), - PaymentStatus.UNPAID, PaymentStatus.PAID, "支付回调成功", null) - ); - + updatePaymentStatus(order.getId(), PaymentStatus.PAID.getCode()); } else if (callbackResponse.isCancel()) { - // 支付取消 - 这种情况下支付状态保持未支付,不需要特别处理 + // 支付取消 statusChangeType = "CANCELLED"; - log.info("支付被取消,支付状态保持未支付: orderId={}", order.getId()); - + updateOrderStatus(order.getId(), OrderStatus.CANCELLED.getCode(), PaymentStatus.UNPAID.getCode()); } else if (callbackResponse.isRefund()) { // 退款 statusChangeType = "REFUNDED"; - updatePaymentStatus(order.getId(), PaymentStatus.REFUNDED.name()); - updateRefundStatus(order.getId(), RefundStatus.FULL_REFUND); - - // 触发退款事件 - orderEventManager.publishRefundStatusChangeEvent( - new RefundStatusChangeEvent(order.getId(), order.getOrderNo(), null, null, - RefundStatus.NO_REFUND, RefundStatus.FULL_REFUND, - order.getFinalAmount(), "支付回调退款", null) - ); + updateRefundStatus(order.getId(), RefundStatus.FULL_REFUND, null, callbackResponse.getTransactionId()); } log.info("支付回调处理成功: orderId={}, orderNo={}, statusChangeType={}", @@ -958,4 +1171,4 @@ public class OrderServiceImpl implements IOrderService { } } } -} \ No newline at end of file +}