diff --git a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/trade/GatewayPayOrderAdminController.java b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/trade/GatewayPayOrderAdminController.java index 9a6f9b16d..6553ce3e9 100644 --- a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/trade/GatewayPayOrderAdminController.java +++ b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/trade/GatewayPayOrderAdminController.java @@ -50,14 +50,4 @@ public class GatewayPayOrderAdminController { @NotNull(message = "{validation.field.id.notNull}") Long id) { return Res.ok(gatewayPayOrderAdminService.sync(id)); } - - @PermCode(code = PermCodes.Action.MANAGE) - @Operation(summary = "关闭订单") - @PostMapping("/close") - public Result close( - @NotNull(message = "{validation.field.id.notNull}") Long id, - @RequestParam(defaultValue = "false") boolean useCancel) { - gatewayPayOrderAdminService.close(id, useCancel); - return Res.ok(); - } } diff --git a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/trade/NormalPayOrderAdminController.java b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/trade/NormalPayOrderAdminController.java index 47441f7fa..610c80df8 100644 --- a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/trade/NormalPayOrderAdminController.java +++ b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/trade/NormalPayOrderAdminController.java @@ -56,14 +56,4 @@ public class NormalPayOrderAdminController { @NotNull(message = "{validation.field.id.notNull}") Long id) { return Res.ok(normalPayOrderAdminService.sync(id)); } - - @PermCode(code = PermCodes.Action.MANAGE) - @Operation(summary = "关闭/撤销订单") - @PostMapping("/close") - public Result close( - @NotNull(message = "{validation.field.id.notNull}") Long id, - @RequestParam(defaultValue = "false") boolean useCancel) { - normalPayOrderAdminService.close(id, useCancel); - return Res.ok(); - } } diff --git a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/trade/OrderCloseAdminController.java b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/trade/OrderCloseAdminController.java new file mode 100644 index 000000000..6042bbac1 --- /dev/null +++ b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/controller/trade/OrderCloseAdminController.java @@ -0,0 +1,38 @@ +package cn.daxpay.open.payment.admin.controller.trade; + +import cn.daxpay.open.payment.admin.service.trade.OrderCloseAdminService; +import cn.daxpay.open.platform.core.annotation.PermCode; +import cn.daxpay.open.platform.core.code.PermCodes; +import cn.daxpay.open.platform.core.rest.Res; +import cn.daxpay.open.platform.core.rest.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/// # 订单关闭(统一入口, 管理) +/// +/// 收归网关支付/普通支付的关闭入口, 前端按 type 区分; 权限由路由层统一校验 trade:order:manage。 +@Validated +@Tag(name = "订单关闭(统一入口)") +@RestController +@RequestMapping("/admin/order") +@RequiredArgsConstructor +public class OrderCloseAdminController { + + private final OrderCloseAdminService orderCloseAdminService; + + @PermCode(menuCode = PermCodes.Trade.Order.MENU, code = PermCodes.Action.MANAGE) + @Operation(summary = "关闭/撤销订单(网关/普通统一入口)") + @PostMapping("/close") + public Result close( + @NotNull(message = "{validation.field.id.notNull}") Long id, + @NotBlank String type, + @RequestParam(defaultValue = "false") boolean useCancel) { + orderCloseAdminService.close(id, type, useCancel); + return Res.ok(); + } +} diff --git a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/GatewayPayOrderAdminService.java b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/GatewayPayOrderAdminService.java index 017a582b4..cbe648faf 100644 --- a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/GatewayPayOrderAdminService.java +++ b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/GatewayPayOrderAdminService.java @@ -1,22 +1,15 @@ package cn.daxpay.open.payment.admin.service.trade; -import cn.daxpay.open.payment.trade.enums.GatewayOrderStatusEnum; import cn.daxpay.open.payment.trade.enums.PayTradeTypeEnum; import cn.daxpay.open.payment.trade.order.convert.GatewayPayOrderConvert; import cn.daxpay.open.payment.trade.order.dao.GatewayPayOrderManager; -import cn.daxpay.open.payment.trade.order.dao.PayTradeManager; import cn.daxpay.open.payment.trade.order.entity.GatewayPayOrder; -import cn.daxpay.open.payment.trade.order.entity.PayTrade; import cn.daxpay.open.payment.trade.order.param.GatewayPayOrderQuery; import cn.daxpay.open.payment.trade.order.result.GatewayPayOrderResult; import cn.daxpay.open.payment.trade.order.service.TradeOrderDetailAssembler; -import cn.daxpay.open.payment.trade.runtime.service.close.PayCloseService; import cn.daxpay.open.payment.trade.runtime.service.sync.PaySyncService; -import cn.daxpay.open.payment.trade.runtime.service.pay.common.PayUniHandleService; import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPaySyncResult; import cn.daxpay.open.platform.common.translate.service.TransService; -import cn.daxpay.open.platform.core.code.CommonErrorCode; -import cn.daxpay.open.platform.core.exception.BizInfoException; import cn.daxpay.open.platform.core.exception.DataNotExistException; import cn.daxpay.open.platform.core.rest.param.PageParam; import cn.daxpay.open.platform.core.rest.result.PageResult; @@ -24,18 +17,13 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.List; - /// # 网关支付业务单管理服务 @Service @RequiredArgsConstructor public class GatewayPayOrderAdminService { private final GatewayPayOrderManager gatewayPayOrderManager; - private final PayTradeManager payTradeManager; private final PaySyncService paySyncService; - private final PayCloseService payCloseService; - private final PayUniHandleService payUniHandleService; private final TransService transService; private final TradeOrderDetailAssembler tradeOrderDetailAssembler; @@ -64,25 +52,6 @@ public class GatewayPayOrderAdminService { } public NormalPaySyncResult sync(Long id) { - PayTrade trade = payTradeManager.findByContainerId(id, PayTradeTypeEnum.GATEWAY.getCode()) - .orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist")); - return paySyncService.syncPayOrder(trade); - } - - public void close(Long id, boolean useCancel) { - GatewayPayOrder order = gatewayPayOrderManager.findById(id) - // 支付: 支付订单不存在 - .orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist")); - if (!List.of(GatewayOrderStatusEnum.WAIT_PAY.getCode(), GatewayOrderStatusEnum.PAYING.getCode()) - .contains(order.getStatus())) { - // 支付: 订单不是支付中无法进行关闭订单 - throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.pay.closeNotPaying"); - } - PayTrade trade = payTradeManager.findByContainerId(id, PayTradeTypeEnum.GATEWAY.getCode()).orElse(null); - if (trade != null) { - payCloseService.closeOrder(trade, useCancel); - } else { - payUniHandleService.gatewayOrderClose(order); - } + return paySyncService.syncByContainer(id, PayTradeTypeEnum.GATEWAY.getCode()); } } diff --git a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/NormalPayOrderAdminService.java b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/NormalPayOrderAdminService.java index 4a0ec4460..abb58a2c6 100644 --- a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/NormalPayOrderAdminService.java +++ b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/NormalPayOrderAdminService.java @@ -3,13 +3,10 @@ package cn.daxpay.open.payment.admin.service.trade; import cn.daxpay.open.payment.trade.enums.PayTradeTypeEnum; import cn.daxpay.open.payment.trade.order.convert.NormalPayOrderConvert; import cn.daxpay.open.payment.trade.order.dao.NormalPayOrderManager; -import cn.daxpay.open.payment.trade.order.dao.PayTradeManager; import cn.daxpay.open.payment.trade.order.entity.NormalPayOrder; -import cn.daxpay.open.payment.trade.order.entity.PayTrade; import cn.daxpay.open.payment.trade.order.param.NormalPayOrderQuery; import cn.daxpay.open.payment.trade.order.result.NormalPayOrderResult; import cn.daxpay.open.payment.trade.order.service.TradeOrderDetailAssembler; -import cn.daxpay.open.payment.trade.runtime.service.close.PayCloseService; import cn.daxpay.open.payment.trade.runtime.service.sync.PaySyncService; import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPaySyncResult; import cn.daxpay.open.platform.common.translate.service.TransService; @@ -28,9 +25,7 @@ import org.springframework.stereotype.Service; public class NormalPayOrderAdminService { private final NormalPayOrderManager normalPayOrderManager; - private final PayTradeManager payTradeManager; private final PaySyncService paySyncService; - private final PayCloseService payCloseService; private final TransService transService; private final TradeOrderDetailAssembler tradeOrderDetailAssembler; @@ -63,15 +58,6 @@ public class NormalPayOrderAdminService { /// 同步支付状态(传入业务订单ID) public NormalPaySyncResult sync(Long id) { - PayTrade trade = payTradeManager.findByContainerId(id, PayTradeTypeEnum.NORMAL.getCode()) - .orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist")); - return paySyncService.syncPayOrder(trade); - } - - /// 关闭/撤销订单(传入业务订单ID) - public void close(Long id, boolean useCancel) { - PayTrade trade = payTradeManager.findByContainerId(id, PayTradeTypeEnum.NORMAL.getCode()) - .orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist")); - payCloseService.closeOrder(trade, useCancel); + return paySyncService.syncByContainer(id, PayTradeTypeEnum.NORMAL.getCode()); } } diff --git a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/OrderCloseAdminService.java b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/OrderCloseAdminService.java new file mode 100644 index 000000000..f739c13dd --- /dev/null +++ b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/OrderCloseAdminService.java @@ -0,0 +1,25 @@ +package cn.daxpay.open.payment.admin.service.trade; + +import cn.daxpay.open.payment.trade.runtime.service.close.PayCloseService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/// # 订单关闭统一管理服务 +/// +/// 收归网关支付与普通支付的关闭入口, 委托 [PayCloseService.closeByContainer]。 +/// 网关预下单未生成 Trade 的兜底关单由 closeByContainer 内部处理, 本服务不重复实现。 +@Service +@RequiredArgsConstructor +public class OrderCloseAdminService { + + private final PayCloseService payCloseService; + + /// 关闭/撤销订单(统一入口) + /// + /// @param id 业务容器ID(pay_gateway_order / pay_normal_order 主键) + /// @param type 容器类型: gateway=网关支付, normal=普通支付 + /// @param useCancel 是否以撤销方式关闭 + public void close(Long id, String type, boolean useCancel) { + payCloseService.closeByContainer(id, type, useCancel); + } +} diff --git a/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/controller/trade/AppAdminGatewayPayOrderController.java b/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/controller/trade/AppAdminGatewayPayOrderController.java index 6d1d51560..7f489cc53 100644 --- a/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/controller/trade/AppAdminGatewayPayOrderController.java +++ b/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/controller/trade/AppAdminGatewayPayOrderController.java @@ -54,14 +54,4 @@ public class AppAdminGatewayPayOrderController { @NotNull(message = "{validation.field.id.notNull}") Long id) { return Res.ok(gatewayPayOrderService.sync(id)); } - - @PermCode(code = PermCodes.Action.MANAGE) - @Operation(summary = "关闭订单") - @PostMapping("/close") - public Result close( - @NotNull(message = "{validation.field.id.notNull}") Long id, - @RequestParam(defaultValue = "false") boolean useCancel) { - gatewayPayOrderService.close(id, useCancel); - return Res.ok(); - } } diff --git a/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/controller/trade/AppAdminNormalPayOrderController.java b/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/controller/trade/AppAdminNormalPayOrderController.java index d3a1e13df..6c8060cdf 100644 --- a/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/controller/trade/AppAdminNormalPayOrderController.java +++ b/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/controller/trade/AppAdminNormalPayOrderController.java @@ -56,14 +56,4 @@ public class AppAdminNormalPayOrderController { @NotNull(message = "{validation.field.id.notNull}") Long id) { return Res.ok(normalPayOrderService.sync(id)); } - - @PermCode(code = PermCodes.Action.MANAGE) - @Operation(summary = "关闭/撤销订单") - @PostMapping("/close") - public Result close( - @NotNull(message = "{validation.field.id.notNull}") Long id, - @RequestParam(defaultValue = "false") boolean useCancel) { - normalPayOrderService.close(id, useCancel); - return Res.ok(); - } } diff --git a/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/controller/trade/AppAdminOrderCloseController.java b/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/controller/trade/AppAdminOrderCloseController.java new file mode 100644 index 000000000..d98f795a8 --- /dev/null +++ b/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/controller/trade/AppAdminOrderCloseController.java @@ -0,0 +1,38 @@ +package cn.daxpay.open.payment.app.admin.controller.trade; + +import cn.daxpay.open.payment.app.admin.service.trade.AppAdminOrderCloseService; +import cn.daxpay.open.platform.core.annotation.PermCode; +import cn.daxpay.open.platform.core.code.PermCodes; +import cn.daxpay.open.platform.core.rest.Res; +import cn.daxpay.open.platform.core.rest.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/// # 订单关闭(统一入口, 运营移动端) +/// +/// 收归网关支付/普通支付的关闭入口, 前端按 type 区分; 权限由路由层统一校验 trade:order:manage。 +@Validated +@Tag(name = "订单关闭(统一入口)") +@RestController +@RequestMapping("/app-admin/order") +@RequiredArgsConstructor +public class AppAdminOrderCloseController { + + private final AppAdminOrderCloseService appAdminOrderCloseService; + + @PermCode(menuCode = PermCodes.Trade.Order.MENU, code = PermCodes.Action.MANAGE) + @Operation(summary = "关闭/撤销订单(网关/普通统一入口)") + @PostMapping("/close") + public Result close( + @NotNull(message = "{validation.field.id.notNull}") Long id, + @NotBlank String type, + @RequestParam(defaultValue = "false") boolean useCancel) { + appAdminOrderCloseService.close(id, type, useCancel); + return Res.ok(); + } +} diff --git a/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/service/trade/AppAdminGatewayPayOrderService.java b/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/service/trade/AppAdminGatewayPayOrderService.java index dceaef519..69993307b 100644 --- a/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/service/trade/AppAdminGatewayPayOrderService.java +++ b/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/service/trade/AppAdminGatewayPayOrderService.java @@ -32,9 +32,4 @@ public class AppAdminGatewayPayOrderService { public NormalPaySyncResult sync(Long id) { return gatewayPayOrderAdminService.sync(id); } - - /// 关闭订单 - public void close(Long id, boolean useCancel) { - gatewayPayOrderAdminService.close(id, useCancel); - } } diff --git a/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/service/trade/AppAdminNormalPayOrderService.java b/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/service/trade/AppAdminNormalPayOrderService.java index 17186f6c9..0db8c9088 100644 --- a/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/service/trade/AppAdminNormalPayOrderService.java +++ b/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/service/trade/AppAdminNormalPayOrderService.java @@ -32,9 +32,4 @@ public class AppAdminNormalPayOrderService { public NormalPaySyncResult sync(Long id) { return normalPayOrderAdminService.sync(id); } - - /// 关闭/撤销订单 - public void close(Long id, boolean useCancel) { - normalPayOrderAdminService.close(id, useCancel); - } } diff --git a/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/service/trade/AppAdminOrderCloseService.java b/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/service/trade/AppAdminOrderCloseService.java new file mode 100644 index 000000000..8741483c2 --- /dev/null +++ b/daxpay-payment/daxpay-payment-app-admin/src/main/java/cn/daxpay/open/payment/app/admin/service/trade/AppAdminOrderCloseService.java @@ -0,0 +1,20 @@ +package cn.daxpay.open.payment.app.admin.service.trade; + +import cn.daxpay.open.payment.admin.service.trade.OrderCloseAdminService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/// # 运营移动端-订单关闭统一服务 +/// +/// 逻辑转发至 [OrderCloseAdminService]。 +@Service +@RequiredArgsConstructor +public class AppAdminOrderCloseService { + + private final OrderCloseAdminService orderCloseAdminService; + + /// 关闭/撤销订单(统一入口) + public void close(Long id, String type, boolean useCancel) { + orderCloseAdminService.close(id, type, useCancel); + } +} diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/close/PayCloseService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/close/PayCloseService.java index 7cfc31cac..dc549fe4d 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/close/PayCloseService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/runtime/service/close/PayCloseService.java @@ -1,6 +1,7 @@ package cn.daxpay.open.payment.trade.runtime.service.close; import cn.daxpay.open.payment.common.context.PaymentContext; +import cn.daxpay.open.payment.trade.enums.GatewayOrderStatusEnum; import cn.daxpay.open.payment.trade.enums.PayFundStatusEnum; import cn.daxpay.open.payment.trade.enums.PayTradeTypeEnum; import cn.daxpay.open.payment.strategy.PaymentStrategyFactory; @@ -54,28 +55,71 @@ public class PayCloseService { private final LockExecutor lockExecutor; private final PaymentContext paymentContext; + /// 按容器ID关闭/撤销订单(含网关预下单未生成 Trade 的兜底) + /// + /// 供容器视角的对外 Service(Admin/Merchant)调用, 消除两端重复的容器状态校验与预下单分支。 + public void closeByContainer(Long containerId, String tradeType, boolean useCancel) { + // 网关: 预下单后可能尚未生成 Trade + if (Objects.equals(tradeType, PayTradeTypeEnum.GATEWAY.getCode())) { + GatewayPayOrder order = gatewayPayOrderManager.findById(containerId) + .orElseThrow(() -> new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.payOrderNotExist")); + if (!List.of(GatewayOrderStatusEnum.WAIT_PAY.getCode(), GatewayOrderStatusEnum.PAYING.getCode()) + .contains(order.getStatus())) { + // 支付: 订单不是支付中, 无法进行关闭订单 + throw new BizInfoException(DaxPayErrorCode.TRADE_STATUS_ERROR, "pay.error.pay.closeNotPaying"); + } + PayTrade trade = payTradeManager.findByContainerId(containerId, tradeType).orElse(null); + if (Objects.nonNull(trade)) { + this.closeOrder(trade, useCancel); + } else { + payUniHandleService.gatewayOrderClose(order); + } + return; + } + // 普通支付等其他形态: 直接反查 Trade 关单 + PayTrade trade = payTradeManager.findByContainerId(containerId, tradeType) + .orElseThrow(() -> new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.payOrderNotExist")); + this.closeOrder(trade, useCancel); + } + /// 关闭支付(商户 API) + /// + /// 同时支持普通支付与网关支付两种容器: + /// - orderNo 优先按 tradeNo(平台支付订单号)查资金交易; 未命中再按网关容器 orderNo 反查(运营友好, 与退款服务对齐) + /// - bizOrderNo 回退: 先普通容器再网关容器, 委托 [closeByContainer] 复用网关预下单无 Trade 的兜底关单 public void close(NormalPayCloseParam param) { if (StrUtil.isBlank(param.getOrderNo()) && Objects.isNull(param.getBizOrderNo())) { throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.orderNoRequired"); } - PayTrade trade = null; + // orderNo 优先: 先按 tradeNo 查资金交易(实体自带 tradeType, 直接 closeOrder) if (StrUtil.isNotBlank(param.getOrderNo())) { - trade = payTradeManager.findByTradeNo(param.getOrderNo()).orElse(null); - } - if (Objects.isNull(trade) && Objects.nonNull(param.getBizOrderNo())) { - NormalPayOrder normalOrder = payNormalOrderManager.findByBizOrderNo(param.getBizOrderNo()) - .orElse(null); - if (Objects.nonNull(normalOrder)) { - trade = payTradeManager.findByContainerId(normalOrder.getId(), PayTradeTypeEnum.NORMAL.getCode()) - .orElse(null); + PayTrade trade = payTradeManager.findByTradeNo(param.getOrderNo()).orElse(null); + if (Objects.nonNull(trade)) { + this.closeOrder(trade, param.isUseCancel()); + return; + } + // tradeNo 未命中: 尝试用网关容器 orderNo 反查 + GatewayPayOrder gateway = gatewayPayOrderManager.findByOrderNo(param.getOrderNo()).orElse(null); + if (Objects.nonNull(gateway)) { + this.closeByContainer(gateway.getId(), PayTradeTypeEnum.GATEWAY.getCode(), param.isUseCancel()); + return; } } - if (Objects.isNull(trade)) { - // 支付: 支付订单不存在 - throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.payOrderNotExist"); + // bizOrderNo 回退: 先普通再网关, 委托 closeByContainer(含网关无 Trade 兜底) + if (Objects.nonNull(param.getBizOrderNo())) { + NormalPayOrder normalOrder = payNormalOrderManager.findByBizOrderNo(param.getBizOrderNo()).orElse(null); + if (Objects.nonNull(normalOrder)) { + this.closeByContainer(normalOrder.getId(), PayTradeTypeEnum.NORMAL.getCode(), param.isUseCancel()); + return; + } + GatewayPayOrder gatewayOrder = gatewayPayOrderManager.findByBizOrderNo(param.getBizOrderNo()).orElse(null); + if (Objects.nonNull(gatewayOrder)) { + this.closeByContainer(gatewayOrder.getId(), PayTradeTypeEnum.GATEWAY.getCode(), param.isUseCancel()); + return; + } } - this.closeOrder(trade, param.isUseCancel()); + // 支付: 支付订单不存在 + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.payOrderNotExist"); } /// 关闭支付记录 diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/pay/NormalPayCloseParam.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/pay/NormalPayCloseParam.java index 042b2826c..eeba45737 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/pay/NormalPayCloseParam.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/pay/NormalPayCloseParam.java @@ -13,12 +13,12 @@ import lombok.EqualsAndHashCode; @Schema(title = "支付关闭参数") public class NormalPayCloseParam extends MerchantPaymentCommonParam { - /// 支付订单号/商户订单号/通道订单号至少要传输一个,支付订单号 > 商户订单号 > 通道订单号 + /// 平台支付订单号(tradeNo)或网关订单号(orderNo), 优先按 tradeNo 查, 未命中按网关 orderNo 反查 @Schema(description = "订单号") @Size(max = 100, message = "{validation.field.orderNo.size}") private String orderNo; - /// 支付订单号/商户订单号/通道订单号至少要传输一个,支付订单号 > 商户订单号 > 通道订单号 + /// 商户订单号, 支持普通支付与网关支付两种容器, 先普通容器再网关容器查询 @Schema(description = "商户订单号") @Size(max = 100, message = "{validation.field.bizOrderNo.size}") private String bizOrderNo; diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/trade/MchGatewayPayOrderController.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/trade/MchGatewayPayOrderController.java index 583d5715e..c5be3150d 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/trade/MchGatewayPayOrderController.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/trade/MchGatewayPayOrderController.java @@ -56,14 +56,4 @@ public class MchGatewayPayOrderController { @NotNull(message = "{validation.field.id.notNull}") Long id) { return Res.ok(mchGatewayPayOrderService.sync(id)); } - - @PermCode(code = PermCodes.Action.MANAGE) - @Operation(summary = "关闭订单") - @PostMapping("/close") - public Result close( - @NotNull(message = "{validation.field.id.notNull}") Long id, - @RequestParam(defaultValue = "false") boolean useCancel) { - mchGatewayPayOrderService.close(id, useCancel); - return Res.ok(); - } } diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/trade/MchNormalPayOrderController.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/trade/MchNormalPayOrderController.java index 4fa9ad31a..de714c0b2 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/trade/MchNormalPayOrderController.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/trade/MchNormalPayOrderController.java @@ -56,14 +56,4 @@ public class MchNormalPayOrderController { @NotNull(message = "{validation.field.id.notNull}") Long id) { return Res.ok(mchNormalPayOrderService.sync(id)); } - - @PermCode(code = PermCodes.Action.MANAGE) - @Operation(summary = "关闭/撤销订单") - @PostMapping("/close") - public Result close( - @NotNull(message = "{validation.field.id.notNull}") Long id, - @RequestParam(defaultValue = "false") boolean useCancel) { - mchNormalPayOrderService.close(id, useCancel); - return Res.ok(); - } } diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/trade/MchOrderCloseController.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/trade/MchOrderCloseController.java new file mode 100644 index 000000000..4533dafcd --- /dev/null +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/trade/MchOrderCloseController.java @@ -0,0 +1,38 @@ +package cn.daxpay.open.payment.merchant.controller.trade; + +import cn.daxpay.open.payment.merchant.service.trade.MchOrderCloseService; +import cn.daxpay.open.platform.core.annotation.PermCode; +import cn.daxpay.open.platform.core.code.PermCodes; +import cn.daxpay.open.platform.core.rest.Res; +import cn.daxpay.open.platform.core.rest.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/// # 订单关闭(统一入口, 商户) +/// +/// 收归网关支付/普通支付的关闭入口, 前端按 type 区分; 权限由路由层统一校验 trade:order:manage。 +@Validated +@Tag(name = "订单关闭(统一入口)") +@RestController +@RequestMapping("/mch/order") +@RequiredArgsConstructor +public class MchOrderCloseController { + + private final MchOrderCloseService mchOrderCloseService; + + @PermCode(menuCode = PermCodes.Trade.Order.MENU, code = PermCodes.Action.MANAGE) + @Operation(summary = "关闭/撤销订单(网关/普通统一入口)") + @PostMapping("/close") + public Result close( + @NotNull(message = "{validation.field.id.notNull}") Long id, + @NotBlank String type, + @RequestParam(defaultValue = "false") boolean useCancel) { + mchOrderCloseService.close(id, type, useCancel); + return Res.ok(); + } +} diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchGatewayPayOrderService.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchGatewayPayOrderService.java index 3d7b5aa95..fa1cac3c7 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchGatewayPayOrderService.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchGatewayPayOrderService.java @@ -1,22 +1,16 @@ package cn.daxpay.open.payment.merchant.service.trade; import cn.daxpay.open.payment.common.context.PaymentContext; -import cn.daxpay.open.payment.trade.enums.GatewayOrderStatusEnum; import cn.daxpay.open.payment.trade.enums.PayTradeTypeEnum; import cn.daxpay.open.payment.trade.order.convert.GatewayPayOrderConvert; import cn.daxpay.open.payment.trade.order.dao.GatewayPayOrderManager; -import cn.daxpay.open.payment.trade.order.dao.PayTradeManager; import cn.daxpay.open.payment.trade.order.entity.GatewayPayOrder; -import cn.daxpay.open.payment.trade.order.entity.PayTrade; import cn.daxpay.open.payment.trade.order.param.GatewayPayOrderQuery; import cn.daxpay.open.payment.trade.order.result.GatewayPayOrderResult; import cn.daxpay.open.payment.trade.order.service.TradeOrderDetailAssembler; -import cn.daxpay.open.payment.trade.runtime.service.close.PayCloseService; -import cn.daxpay.open.payment.trade.runtime.service.pay.common.PayUniHandleService; import cn.daxpay.open.payment.trade.runtime.service.sync.PaySyncService; import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPaySyncResult; import cn.daxpay.open.platform.core.code.CommonCode; -import cn.daxpay.open.platform.core.code.CommonErrorCode; import cn.daxpay.open.platform.core.exception.BizInfoException; import cn.daxpay.open.platform.core.exception.DataNotExistException; import cn.daxpay.open.platform.core.rest.param.PageParam; @@ -25,8 +19,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.List; - /// # 网关支付业务单(商户端) /// /// 强制按 [PaymentContext#getMchNo] 过滤;行级隔离另由 TenantLine 兜底。 @@ -36,10 +28,7 @@ public class MchGatewayPayOrderService { private final PaymentContext paymentContext; private final GatewayPayOrderManager gatewayPayOrderManager; - private final PayTradeManager payTradeManager; private final PaySyncService paySyncService; - private final PayCloseService payCloseService; - private final PayUniHandleService payUniHandleService; private final TradeOrderDetailAssembler tradeOrderDetailAssembler; /// 分页查询(强制当前商户) @@ -67,27 +56,7 @@ public class MchGatewayPayOrderService { /// 同步支付状态 public NormalPaySyncResult sync(Long id) { - PayTrade trade = payTradeManager.findByContainerId(id, PayTradeTypeEnum.GATEWAY.getCode()) - .orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist")); - return paySyncService.syncPayOrder(trade); - } - - /// 关闭/撤销订单 - public void close(Long id, boolean useCancel) { - GatewayPayOrder order = gatewayPayOrderManager.findById(id) - // 支付: 支付订单不存在 - .orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist")); - if (!List.of(GatewayOrderStatusEnum.WAIT_PAY.getCode(), GatewayOrderStatusEnum.PAYING.getCode()) - .contains(order.getStatus())) { - // 支付: 订单不是支付中无法进行关闭订单 - throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.pay.closeNotPaying"); - } - PayTrade trade = payTradeManager.findByContainerId(id, PayTradeTypeEnum.GATEWAY.getCode()).orElse(null); - if (trade != null) { - payCloseService.closeOrder(trade, useCancel); - } else { - payUniHandleService.gatewayOrderClose(order); - } + return paySyncService.syncByContainer(id, PayTradeTypeEnum.GATEWAY.getCode()); } /// 解析并强制写入当前商户号 diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchNormalPayOrderService.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchNormalPayOrderService.java index 7b843a4ac..f3a4001a6 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchNormalPayOrderService.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchNormalPayOrderService.java @@ -4,13 +4,10 @@ import cn.daxpay.open.payment.common.context.PaymentContext; import cn.daxpay.open.payment.trade.enums.PayTradeTypeEnum; import cn.daxpay.open.payment.trade.order.convert.NormalPayOrderConvert; import cn.daxpay.open.payment.trade.order.dao.NormalPayOrderManager; -import cn.daxpay.open.payment.trade.order.dao.PayTradeManager; import cn.daxpay.open.payment.trade.order.entity.NormalPayOrder; -import cn.daxpay.open.payment.trade.order.entity.PayTrade; import cn.daxpay.open.payment.trade.order.param.NormalPayOrderQuery; import cn.daxpay.open.payment.trade.order.result.NormalPayOrderResult; import cn.daxpay.open.payment.trade.order.service.TradeOrderDetailAssembler; -import cn.daxpay.open.payment.trade.runtime.service.close.PayCloseService; import cn.daxpay.open.payment.trade.runtime.service.sync.PaySyncService; import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPaySyncResult; import cn.daxpay.open.platform.core.code.CommonCode; @@ -31,9 +28,7 @@ public class MchNormalPayOrderService { private final PaymentContext paymentContext; private final NormalPayOrderManager normalPayOrderManager; - private final PayTradeManager payTradeManager; private final PaySyncService paySyncService; - private final PayCloseService payCloseService; private final TradeOrderDetailAssembler tradeOrderDetailAssembler; /// 分页查询(强制当前商户) @@ -61,16 +56,7 @@ public class MchNormalPayOrderService { /// 同步支付状态(传入业务订单ID) public NormalPaySyncResult sync(Long id) { - PayTrade trade = payTradeManager.findByContainerId(id, PayTradeTypeEnum.NORMAL.getCode()) - .orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist")); - return paySyncService.syncPayOrder(trade); - } - - /// 关闭/撤销订单(传入业务订单ID) - public void close(Long id, boolean useCancel) { - PayTrade trade = payTradeManager.findByContainerId(id, PayTradeTypeEnum.NORMAL.getCode()) - .orElseThrow(() -> new DataNotExistException("pay.error.payOrderNotExist")); - payCloseService.closeOrder(trade, useCancel); + return paySyncService.syncByContainer(id, PayTradeTypeEnum.NORMAL.getCode()); } /// 解析并强制写入当前商户号 diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchOrderCloseService.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchOrderCloseService.java new file mode 100644 index 000000000..dfee9e123 --- /dev/null +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchOrderCloseService.java @@ -0,0 +1,24 @@ +package cn.daxpay.open.payment.merchant.service.trade; + +import cn.daxpay.open.payment.trade.runtime.service.close.PayCloseService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/// # 订单关闭统一管理服务(商户端) +/// +/// 收归网关支付与普通支付的关闭入口, 委托 [PayCloseService#closeByContainer]。 +@Service +@RequiredArgsConstructor +public class MchOrderCloseService { + + private final PayCloseService payCloseService; + + /// 关闭/撤销订单(统一入口) + /// + /// @param id 业务容器ID(pay_gateway_order / pay_normal_order 主键) + /// @param type 容器类型: gateway=网关支付, normal=普通支付 + /// @param useCancel 是否以撤销方式关闭 + public void close(Long id, String type, boolean useCancel) { + payCloseService.closeByContainer(id, type, useCancel); + } +} diff --git a/daxpay-start/src/main/resources/application-dev.yml b/daxpay-start/src/main/resources/application-dev.yml index 72a171629..e73bdf6e8 100644 --- a/daxpay-start/src/main/resources/application-dev.yml +++ b/daxpay-start/src/main/resources/application-dev.yml @@ -100,7 +100,6 @@ daxpay: - '/test/**' - '/error' - '/favicon.ico' - - '/**' # 审计日志配置 audit-log: ip2region: @@ -109,6 +108,8 @@ daxpay: # 查询模式: VECTOR_INDEX(推荐) 或 CACHE search-type: VECTOR_INDEX config: + # 交易定时同步开关(dev 环境开启) + trade-sync-enabled: true # 平台公私钥(证书)配置, 用于接口请求/回调的 RSA 签名与验签 key-config: # 私钥配置