diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopOrderController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderController.java index 2e95ea8..4a10e2a 100644 --- a/src/main/java/com/gxwebsoft/shop/controller/ShopOrderController.java +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderController.java @@ -19,6 +19,7 @@ import com.gxwebsoft.shop.task.OrderAutoCancelTask; import com.gxwebsoft.shop.entity.ShopOrder; import com.gxwebsoft.shop.param.ShopOrderParam; import com.gxwebsoft.shop.dto.OrderCreateRequest; +import com.gxwebsoft.shop.dto.OrderPrepayRequest; import com.gxwebsoft.shop.dto.UpdatePaymentStatusRequest; import com.gxwebsoft.payment.service.PaymentService; import com.gxwebsoft.payment.dto.PaymentResponse; @@ -170,6 +171,92 @@ public class ShopOrderController extends BaseController { return fail("添加失败"); } + @Operation(summary = "发起支付/重新支付(兼容 pay/prepay/repay)") + @PostMapping({"/pay", "/prepay", "/repay"}) + public ApiResult prepay(@RequestBody OrderPrepayRequest request) { + User loginUser = getLoginUser(); + if (loginUser == null) { + return fail("用户未登录"); + } + + // 允许从请求显式传 tenantId(兼容一些历史调用),否则优先从请求头/登录态推断 + Integer tenantId = request != null ? request.getTenantId() : null; + if (tenantId == null) { + tenantId = ObjectUtil.defaultIfNull(getTenantId(), loginUser.getTenantId()); + } + + if (request == null || (request.getOrderId() == null && StrUtil.isBlank(request.getOrderNo()))) { + return fail("orderId 或 orderNo 不能为空"); + } + + ShopOrder order; + if (request.getOrderId() != null) { + order = shopOrderService.getById(request.getOrderId()); + } else { + if (tenantId == null) { + return fail("tenantId 不能为空"); + } + order = shopOrderService.getByOrderNo(request.getOrderNo(), tenantId); + } + if (order == null) { + return fail("订单不存在"); + } + + // 校验租户(避免用别的租户订单号撞库) + if (tenantId != null && order.getTenantId() != null && !tenantId.equals(order.getTenantId())) { + return fail("订单不存在"); + } + + // 普通用户只能操作自己的订单;管理员可越权(复用取消权限判断即可) + if (!loginUser.getUserId().equals(order.getUserId()) && !hasOrderCancelAuthority()) { + return fail("无权限操作此订单"); + } + + // 业务状态校验:这些错误需要明确返回(code!=0),避免前端误判为接口不支持而降级走创建订单 + if (Boolean.TRUE.equals(order.getPayStatus())) { + return fail("订单已支付"); + } + if (order.getDeleted() != null && order.getDeleted() == 1) { + return fail("订单已删除"); + } + if (order.getOrderStatus() != null) { + // 2=已取消;6=退款成功;7=客户端申请退款;其他非0状态也视为不可再次发起支付 + if (!Objects.equals(order.getOrderStatus(), 0)) { + return fail("订单状态不允许发起支付"); + } + } + if (order.getExpirationTime() != null && order.getExpirationTime().isBefore(LocalDateTime.now())) { + return fail("订单已过期"); + } + + // 补齐 createWxOrder 所需字段(注意:openid 在 ShopOrder 上是非持久化字段,查询出来为空) + Integer payType = request.getPayType() != null ? request.getPayType() : order.getPayType(); + if (payType == null) { + payType = 1; + } + if (!Objects.equals(payType, 1) && !Objects.equals(payType, 102)) { + return fail("该订单不支持发起微信支付"); + } + order.setPayType(payType); + order.setPayUserId(loginUser.getUserId()); + order.setOpenid(loginUser.getOpenid()); + if (order.getPayPrice() == null) { + order.setPayPrice(ObjectUtil.defaultIfNull(order.getTotalPrice(), BigDecimal.ZERO)); + } + if (StrUtil.isBlank(order.getComments())) { + order.setComments("订单支付"); + } + + try { + Map wxOrderInfo = shopOrderService.createWxOrder(order); + return success(wxOrderInfo); + } catch (Exception e) { + logger.error("发起支付失败 - userId={}, orderId={}, orderNo={}", + loginUser.getUserId(), order.getOrderId(), order.getOrderNo(), e); + return fail("发起支付失败:" + e.getMessage()); + } + } + @PreAuthorize("hasAuthority('shop:shopOrder:update')") @Operation(summary = "修改订单") @PutMapping() diff --git a/src/main/java/com/gxwebsoft/shop/dto/OrderPrepayRequest.java b/src/main/java/com/gxwebsoft/shop/dto/OrderPrepayRequest.java new file mode 100644 index 0000000..9690c6b --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/dto/OrderPrepayRequest.java @@ -0,0 +1,32 @@ +package com.gxwebsoft.shop.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.Positive; + +/** + * 订单重新发起支付请求DTO + * + * 前端会按 /shop/shop-order/pay -> /prepay -> /repay 依次尝试。 + * 后端可统一实现为同一套逻辑(多个URL别名),避免出现 404 导致前端误判为不支持接口。 + */ +@Data +@Schema(name = "OrderPrepayRequest", description = "订单重新发起支付请求") +public class OrderPrepayRequest { + + @Schema(description = "订单ID(二选一:orderId 或 orderNo)") + @Positive(message = "订单ID必须为正数") + private Integer orderId; + + @Schema(description = "订单号(二选一:orderId 或 orderNo)") + private String orderNo; + + @Schema(description = "支付方式:1=微信支付,102=微信Native(兼容旧类型)。不传则使用订单原支付方式/默认微信支付") + private Integer payType; + + @Schema(description = "租户ID(可选;不传则从当前登录用户/请求头推断)") + @Positive(message = "租户ID必须为正数") + private Integer tenantId; +} +