Compare commits

...

2 Commits

Author SHA1 Message Date
51d3a029cc feat(shop): 添加订单支付功能支持
- 新增 OrderPrepayRequest DTO 用于处理支付请求参数
- 实现 prepay 接口支持 /pay、/prepay、/repay 多路径兼容
- 添加用户登录验证和租户权限校验机制
- 集成微信支付创建订单功能并返回支付信息
- 实现订单状态验证包括已支付、已删除、已过期等状态检查
- 支持通过订单ID或订单号查询并处理支付请求
- 添加支付类型参数处理和默认值设置逻辑
2026-02-04 15:57:34 +08:00
30c7e72a80 fix(order): 修复订单处理中的空指针异常和状态比较问题
- 添加 Objects 工具类导入用于安全的对象比较
- 修复 shopOrderNow 为空时的空指针异常
- 使用 Objects.equals 替换直接的 equals 比较避免 NPE
- 为发货状态变更逻辑添加清晰的注释说明
- 修复支付状态检查中的布尔值比较逻辑
2026-02-04 15:47:23 +08:00
2 changed files with 128 additions and 4 deletions

View File

@@ -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;
@@ -48,6 +49,7 @@ import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 订单控制器
@@ -169,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<String, String> 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()
@@ -178,11 +266,15 @@ public class ShopOrderController extends BaseController {
return fail("订单不存在");
}
ShopOrder shopOrderNow = shopOrderService.getById(shopOrder.getOrderId());
if (shopOrderNow == null) {
return fail("订单不存在");
}
// 申请退款
if (shopOrder.getOrderStatus().equals(4)) {
if (Objects.equals(shopOrder.getOrderStatus(), 4)) {
shopOrder.setRefundApplyTime(LocalDateTime.now());
}
if (shopOrderNow.getDeliveryStatus().equals(10) && shopOrder.getDeliveryStatus().equals(20)) {
// 发货状态从“未发货(10)”变更为“已发货(20)”时,记录发货信息
if (Objects.equals(shopOrderNow.getDeliveryStatus(), 10) && Objects.equals(shopOrder.getDeliveryStatus(), 20)) {
ShopOrderDelivery shopOrderDelivery = new ShopOrderDelivery();
shopOrderDelivery.setOrderId(shopOrder.getOrderId());
shopOrderDelivery.setDeliveryMethod(30);
@@ -196,12 +288,12 @@ public class ShopOrderController extends BaseController {
}
// 退款操作
if(shopOrder.getOrderStatus().equals(6)){
if (Objects.equals(shopOrder.getOrderStatus(), 6)) {
// 当订单状态更改为6已退款执行退款操作
try {
// 检查订单是否已支付
if (!shopOrder.getPayStatus()) {
if (!Boolean.TRUE.equals(shopOrderNow.getPayStatus())) {
return fail("订单未支付,无法退款");
}

View File

@@ -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;
}