From e27748840ff98a7cb6cd7a302105ebd8f44be1b5 Mon Sep 17 00:00:00 2001 From: daxpay Date: Sun, 2 Aug 2026 10:43:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(payment):=20=E7=BB=9F=E4=B8=80=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E6=96=B0=E5=A2=9E=E9=80=80=E6=AC=BE/=E9=80=80?= =?UTF-8?q?=E6=AC=BE=E6=9F=A5=E8=AF=A2/=E9=80=80=E6=AC=BE=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UniTradeController 增 POST /refund 退款发起 - UniQueryController 增 POST /refund-order 退款订单查询 - UniSyncController 增 POST /refund 退款订单同步 - 新增 RefundParam/RefundOrderQueryParam/RefundSyncParam 与对应 Result - 新增 RefundOrderService/RefundOrderQueryService/RefundOrderSyncService + UnipayRefundOrderConvert - RefundOrderManager 增 findByBizRefundNo(单参/含appId) 支持统一接口主路径查询 --- .../trade/order/dao/RefundOrderManager.java | 15 +++++ .../trade/refund/RefundOrderQueryParam.java | 28 ++++++++ .../param/trade/refund/RefundParam.java | 50 +++++++++++++++ .../param/trade/refund/RefundSyncParam.java | 28 ++++++++ .../trade/refund/RefundOrderResult.java | 64 +++++++++++++++++++ .../result/trade/refund/RefundResult.java | 32 ++++++++++ .../result/trade/refund/RefundSyncResult.java | 24 +++++++ .../trade/controller/UniQueryController.java | 10 +++ .../trade/controller/UniSyncController.java | 10 +++ .../trade/controller/UniTradeController.java | 12 +++- .../convert/UnipayRefundOrderConvert.java | 29 +++++++++ .../service/RefundOrderQueryService.java | 49 ++++++++++++++ .../trade/service/RefundOrderService.java | 28 ++++++++ .../trade/service/RefundOrderSyncService.java | 59 +++++++++++++++++ 14 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/refund/RefundOrderQueryParam.java create mode 100644 daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/refund/RefundParam.java create mode 100644 daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/refund/RefundSyncParam.java create mode 100644 daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/refund/RefundOrderResult.java create mode 100644 daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/refund/RefundResult.java create mode 100644 daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/refund/RefundSyncResult.java create mode 100644 daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/convert/UnipayRefundOrderConvert.java create mode 100644 daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/service/RefundOrderQueryService.java create mode 100644 daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/service/RefundOrderService.java create mode 100644 daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/service/RefundOrderSyncService.java diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/dao/RefundOrderManager.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/dao/RefundOrderManager.java index 823f7a850..073c4d558 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/dao/RefundOrderManager.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/dao/RefundOrderManager.java @@ -23,6 +23,21 @@ public class RefundOrderManager extends BaseManager findByBizRefundNo(String bizRefundNo) { + return lambdaQuery() + .eq(RefundOrder::getBizRefundNo, bizRefundNo) + .oneOpt(); + } + + /// 根据商户退款号和应用号查询(统一接口主路径, 避免同商户多应用串单) + public Optional findByBizRefundNo(String bizRefundNo, String appId) { + return lambdaQuery() + .eq(RefundOrder::getBizRefundNo, bizRefundNo) + .eq(RefundOrder::getAppId, appId) + .oneOpt(); + } + /// 根据实际上送串查询(回调容错: 特殊通道仅回传变形号) public Optional findByRelationOrderNo(String relationOrderNo) { return findByField(RefundOrder::getRelationOrderNo, relationOrderNo); diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/refund/RefundOrderQueryParam.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/refund/RefundOrderQueryParam.java new file mode 100644 index 000000000..b6d3436e3 --- /dev/null +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/refund/RefundOrderQueryParam.java @@ -0,0 +1,28 @@ +package cn.daxpay.open.payment.unipay.param.trade.refund; + +import cn.daxpay.open.payment.unipay.param.MerchantPaymentCommonParam; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/// # 退款单查询参数 +/// +/// 平台退款号与商户退款号至少传一个, 优先使用平台退款号。 +/// 仅查询本地退款单, 不调用通道; 需要实时通道状态请走退款同步接口。 +@EqualsAndHashCode(callSuper = true) +@Data +@Schema(title = "退款单查询参数") +public class RefundOrderQueryParam extends MerchantPaymentCommonParam { + + /// 平台退款号 + @Schema(description = "平台退款号") + @Size(max = 100, message = "{validation.field.refundNo.size}") + private String refundNo; + + /// 商户退款号 + @Schema(description = "商户退款号") + @Size(max = 100, message = "{validation.field.bizRefundNo.size}") + private String bizRefundNo; + +} diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/refund/RefundParam.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/refund/RefundParam.java new file mode 100644 index 000000000..fe944248a --- /dev/null +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/refund/RefundParam.java @@ -0,0 +1,50 @@ +package cn.daxpay.open.payment.unipay.param.trade.refund; + +import cn.daxpay.open.payment.unipay.param.MerchantPaymentCommonParam; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/// # 统一退款发起参数 +/// +/// 从已支付订单发起退款, 支持部分退款。 +/// 原支付单定位: tradeNo(资金交易号) 与 bizOrderNo(商户业务单号) 至少传一个, 优先使用 tradeNo。 +/// 与内部编排参数 [cn.daxpay.open.payment.trade.runtime.param.RefundParam] 同名但职责不同: +/// 本类是对外签名 DTO(含 mchNo/appId/sign), Controller 层负责转换。 +@EqualsAndHashCode(callSuper = true) +@Data +@Schema(title = "统一退款参数") +public class RefundParam extends MerchantPaymentCommonParam { + + /// 原支付资金交易号(平台 tradeNo) + @Schema(description = "原支付资金交易号") + @Size(max = 100, message = "{validation.field.tradeNo.size}") + private String tradeNo; + + /// 原支付商户业务订单号 + @Schema(description = "原支付商户业务订单号") + @Size(max = 100, message = "{validation.field.bizOrderNo.size}") + private String bizOrderNo; + + /// 退款金额(单位: 分, 最小货币单位) + @Schema(description = "退款金额(分)") + @NotNull(message = "{validation.field.amount.notNull}") + @Positive(message = "{validation.field.amount.positive}") + @Min(value = 1, message = "{validation.field.amount.min}") + private Long amount; + + /// 退款原因 + @Schema(description = "退款原因") + @Size(max = 50, message = "{validation.field.reason.size}") + private String reason; + + /// 商户退款号(可选, 不传则由系统生成) + @Schema(description = "商户退款号") + @Size(max = 100, message = "{validation.field.bizRefundNo.size}") + private String bizRefundNo; + +} diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/refund/RefundSyncParam.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/refund/RefundSyncParam.java new file mode 100644 index 000000000..1f82ec4a1 --- /dev/null +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/param/trade/refund/RefundSyncParam.java @@ -0,0 +1,28 @@ +package cn.daxpay.open.payment.unipay.param.trade.refund; + +import cn.daxpay.open.payment.unipay.param.MerchantPaymentCommonParam; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/// # 退款状态同步参数 +/// +/// 主动查询通道网关方退款终态并回写本地退款单。 +/// 平台退款号与商户退款号至少传一个, 优先使用平台退款号。 +@EqualsAndHashCode(callSuper = true) +@Data +@Schema(title = "退款状态同步参数") +public class RefundSyncParam extends MerchantPaymentCommonParam { + + /// 平台退款号 + @Schema(description = "平台退款号") + @Size(max = 100, message = "{validation.field.refundNo.size}") + private String refundNo; + + /// 商户退款号 + @Schema(description = "商户退款号") + @Size(max = 100, message = "{validation.field.bizRefundNo.size}") + private String bizRefundNo; + +} diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/refund/RefundOrderResult.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/refund/RefundOrderResult.java new file mode 100644 index 000000000..639f423af --- /dev/null +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/refund/RefundOrderResult.java @@ -0,0 +1,64 @@ +package cn.daxpay.open.payment.unipay.result.trade.refund; + +import cn.daxpay.open.payment.trade.enums.RefundOrderStatusEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.time.OffsetDateTime; + +/// # 退款订单(统一查询) +/// +/// 对外查询结果(精简), 不复用管理端 [cn.daxpay.open.payment.trade.order.result.RefundOrderResult] +/// (后者含 mchName 翻译、channelMchNo 等内部字段, 不宜对商户暴露)。 +@Data +@Accessors(chain = true) +@Schema(title = "退款订单") +public class RefundOrderResult { + + /// 平台退款号 + @Schema(description = "平台退款号") + private String refundNo; + + /// 商户退款号 + @Schema(description = "商户退款号") + private String bizRefundNo; + + /// 原支付资金交易号 + @Schema(description = "原支付资金交易号") + private String tradeNo; + + /// 原支付商户业务订单号 + @Schema(description = "原支付商户业务订单号") + private String bizOrderNo; + + /// 通道退款流水号 + @Schema(description = "通道退款流水号") + private String outRefundNo; + + /// 退款金额(分) + @Schema(description = "退款金额(分)") + private Long amount; + + /// 订单总金额(分) + @Schema(description = "订单总金额(分)") + private Long orderAmount; + + /// 退款状态 + /// @see RefundOrderStatusEnum + @Schema(description = "退款状态") + private String status; + + /// 退款原因 + @Schema(description = "退款原因") + private String reason; + + /// 退款完成时间(UTC) + @Schema(description = "退款完成时间(UTC)") + private OffsetDateTime finishTime; + + /// 错误信息 + @Schema(description = "错误信息") + private String errorMsg; + +} diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/refund/RefundResult.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/refund/RefundResult.java new file mode 100644 index 000000000..ba096d1d3 --- /dev/null +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/refund/RefundResult.java @@ -0,0 +1,32 @@ +package cn.daxpay.open.payment.unipay.result.trade.refund; + +import cn.daxpay.open.payment.trade.enums.RefundOrderStatusEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 统一退款响应参数 +/// +@Data +@Accessors(chain = true) +@Schema(title = "统一退款响应参数") +public class RefundResult { + + /// 平台退款号 + @Schema(description = "平台退款号") + private String refundNo; + + /// 商户退款号 + @Schema(description = "商户退款号") + private String bizRefundNo; + + /// 退款状态 + /// @see RefundOrderStatusEnum + @Schema(description = "退款状态") + private String status; + + /// 错误信息(失败时返回) + @Schema(description = "错误信息") + private String errorMsg; + +} diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/refund/RefundSyncResult.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/refund/RefundSyncResult.java new file mode 100644 index 000000000..0a495be71 --- /dev/null +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/unipay/result/trade/refund/RefundSyncResult.java @@ -0,0 +1,24 @@ +package cn.daxpay.open.payment.unipay.result.trade.refund; + +import cn.daxpay.open.payment.trade.enums.RefundOrderStatusEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 退款同步结果 +/// +@Data +@Accessors(chain = true) +@Schema(title = "退款同步结果") +public class RefundSyncResult { + + /// 同步后的退款订单状态 + /// @see RefundOrderStatusEnum + @Schema(description = "同步后退款状态") + private String orderStatus; + + /// 是否触发了调整(本地状态因本次同步发生了变更) + @Schema(description = "是否触发了调整") + private boolean adjust; + +} diff --git a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/UniQueryController.java b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/UniQueryController.java index a1e9b7a9e..571b474c9 100644 --- a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/UniQueryController.java +++ b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/UniQueryController.java @@ -2,12 +2,15 @@ package cn.daxpay.open.payment.unipay.trade.controller; import cn.daxpay.open.platform.core.annotation.IgnoreAuth; import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayQueryParam; +import cn.daxpay.open.payment.unipay.param.trade.refund.RefundOrderQueryParam; import cn.daxpay.open.payment.common.result.DaxResult; import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayOrderResult; +import cn.daxpay.open.payment.unipay.result.trade.refund.RefundOrderResult; import cn.daxpay.open.payment.common.util.DaxRes; import cn.daxpay.open.payment.unipay.aop.PaymentVerify; import cn.daxpay.open.payment.unipay.trade.service.NormalPayOrderQueryService; +import cn.daxpay.open.payment.unipay.trade.service.RefundOrderQueryService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -27,6 +30,7 @@ import org.springframework.web.bind.annotation.RestController; public class UniQueryController { private final NormalPayOrderQueryService normalPayOrderQueryService; + private final RefundOrderQueryService refundOrderQueryService; @Operation(summary = "支付订单查询接口") @PostMapping("/pay-order") @@ -34,4 +38,10 @@ public class UniQueryController { return DaxRes.ok(normalPayOrderQueryService.queryPayOrder(param)); } + @Operation(summary = "退款订单查询接口") + @PostMapping("/refund-order") + public DaxResult queryRefundOrder(@RequestBody RefundOrderQueryParam param){ + return DaxRes.ok(refundOrderQueryService.queryRefundOrder(param)); + } + } diff --git a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/UniSyncController.java b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/UniSyncController.java index a9e2dd4e4..182db8888 100644 --- a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/UniSyncController.java +++ b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/UniSyncController.java @@ -2,12 +2,15 @@ package cn.daxpay.open.payment.unipay.trade.controller; import cn.daxpay.open.platform.core.annotation.IgnoreAuth; import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPaySyncParam; +import cn.daxpay.open.payment.unipay.param.trade.refund.RefundSyncParam; import cn.daxpay.open.payment.common.result.DaxResult; import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPaySyncResult; +import cn.daxpay.open.payment.unipay.result.trade.refund.RefundSyncResult; import cn.daxpay.open.payment.common.util.DaxRes; import cn.daxpay.open.payment.unipay.aop.PaymentVerify; import cn.daxpay.open.payment.trade.runtime.service.sync.PaySyncService; +import cn.daxpay.open.payment.unipay.trade.service.RefundOrderSyncService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -28,6 +31,7 @@ import org.springframework.web.bind.annotation.RestController; public class UniSyncController { private final PaySyncService paySyncService; + private final RefundOrderSyncService refundOrderSyncService; @Operation(summary = "支付订单同步接口") @PostMapping("/pay") @@ -35,4 +39,10 @@ public class UniSyncController { return DaxRes.ok(paySyncService.sync(param)); } + @Operation(summary = "退款订单同步接口") + @PostMapping("/refund") + public DaxResult refund(@RequestBody RefundSyncParam param){ + return DaxRes.ok(refundOrderSyncService.sync(param)); + } + } diff --git a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/UniTradeController.java b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/UniTradeController.java index 6431c12b5..fd0b1c9ee 100644 --- a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/UniTradeController.java +++ b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/controller/UniTradeController.java @@ -3,12 +3,15 @@ package cn.daxpay.open.payment.unipay.trade.controller; import cn.daxpay.open.platform.core.annotation.IgnoreAuth; import cn.daxpay.open.payment.common.result.DaxResult; import cn.daxpay.open.payment.common.util.DaxRes; -import cn.daxpay.open.payment.unipay.aop.PaymentVerify; import cn.daxpay.open.payment.trade.runtime.service.close.PayCloseService; import cn.daxpay.open.payment.trade.runtime.service.pay.normal.NormalPayService; +import cn.daxpay.open.payment.unipay.aop.PaymentVerify; import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayCloseParam; import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam; +import cn.daxpay.open.payment.unipay.param.trade.refund.RefundParam; import cn.daxpay.open.payment.unipay.result.trade.pay.NormalPayResult; +import cn.daxpay.open.payment.unipay.result.trade.refund.RefundResult; +import cn.daxpay.open.payment.unipay.trade.service.RefundOrderService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -28,6 +31,7 @@ import org.springframework.web.bind.annotation.RestController; public class UniTradeController { private final NormalPayService normalPayService; private final PayCloseService payCloseService; + private final RefundOrderService refundOrderService; @Operation(summary = "支付接口") @PostMapping("/pay") @@ -42,4 +46,10 @@ public class UniTradeController { return DaxRes.ok(); } + @Operation(summary = "退款接口") + @PostMapping("/refund") + public DaxResult refund(@RequestBody RefundParam param){ + return DaxRes.ok(refundOrderService.refund(param)); + } + } diff --git a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/convert/UnipayRefundOrderConvert.java b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/convert/UnipayRefundOrderConvert.java new file mode 100644 index 000000000..c290c6a01 --- /dev/null +++ b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/convert/UnipayRefundOrderConvert.java @@ -0,0 +1,29 @@ +package cn.daxpay.open.payment.unipay.trade.convert; + +import cn.daxpay.open.payment.trade.order.entity.RefundOrder; +import cn.daxpay.open.payment.unipay.param.trade.refund.RefundParam; +import cn.daxpay.open.payment.unipay.result.trade.refund.RefundOrderResult; +import cn.daxpay.open.payment.unipay.result.trade.refund.RefundResult; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/// # 退款转换器(对外) +/// +/// 汇聚退款场景的对外映射: +/// - 退款单实体 → 对外查询结果 [RefundOrderResult] (内部字段 mchNo/channelMchNo 等自动忽略) +/// - 退款单实体 → 发起响应 [RefundResult] (仅退款号/状态等对外字段) +/// - 对外签名入参 [RefundParam] → 内部编排参数 (丢弃 mchNo/appId/sign 等签名字段) +@Mapper +public interface UnipayRefundOrderConvert { + + UnipayRefundOrderConvert CONVERT = Mappers.getMapper(UnipayRefundOrderConvert.class); + + /// 退款单实体 → 对外查询结果(同名映射) + RefundOrderResult toResult(RefundOrder order); + + /// 退款单实体 → 发起响应(同名映射) + RefundResult toRefundResult(RefundOrder order); + + /// 对外签名入参 → 内部编排参数(同名字段映射, 签名/商户字段无目标自动丢弃) + cn.daxpay.open.payment.trade.runtime.param.RefundParam toRuntime(RefundParam param); +} diff --git a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/service/RefundOrderQueryService.java b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/service/RefundOrderQueryService.java new file mode 100644 index 000000000..a2b4fd558 --- /dev/null +++ b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/service/RefundOrderQueryService.java @@ -0,0 +1,49 @@ +package cn.daxpay.open.payment.unipay.trade.service; + +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.payment.trade.order.dao.RefundOrderManager; +import cn.daxpay.open.payment.trade.order.entity.RefundOrder; +import cn.daxpay.open.payment.unipay.param.trade.refund.RefundOrderQueryParam; +import cn.daxpay.open.payment.unipay.result.trade.refund.RefundOrderResult; +import cn.daxpay.open.payment.unipay.trade.convert.UnipayRefundOrderConvert; +import cn.hutool.core.util.StrUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/// # 退款订单查询服务(对外) +/// +/// 纯查本地退款单, 不调用通道; 需要实时通道状态请走退款同步接口。 +/// 支持按平台退款号(refundNo)或商户退款号(bizRefundNo)查询, 优先使用平台退款号。 +/// 按商户退款号查询时绑定 appId, 避免同商户多应用串单。 +@Slf4j +@Service +@RequiredArgsConstructor +public class RefundOrderQueryService { + + private final RefundOrderManager refundOrderManager; + + /// 查询退款订单 + public RefundOrderResult queryRefundOrder(RefundOrderQueryParam param) { + // 校验参数, 平台退款号和商户退款号不能都为空 + if (StrUtil.isBlank(param.getRefundNo()) && StrUtil.isBlank(param.getBizRefundNo())) { + // 退款: 退款号不能都为空(复用统一接口层通用单号校验 key) + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.orderNoRequired"); + } + + RefundOrder order; + // 优先按平台退款号查询 + if (StrUtil.isNotBlank(param.getRefundNo())) { + order = refundOrderManager.findByRefundNo(param.getRefundNo()) + // 退款: 退款订单不存在 + .orElseThrow(() -> new DataNotExistException("pay.error.refund.orderNotFound")); + } else { + // 按商户退款号 + 应用号查询(避免串单) + order = refundOrderManager.findByBizRefundNo(param.getBizRefundNo(), param.getAppId()) + .orElseThrow(() -> new DataNotExistException("pay.error.refund.orderNotFound")); + } + return UnipayRefundOrderConvert.CONVERT.toResult(order); + } +} diff --git a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/service/RefundOrderService.java b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/service/RefundOrderService.java new file mode 100644 index 000000000..882caebeb --- /dev/null +++ b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/service/RefundOrderService.java @@ -0,0 +1,28 @@ +package cn.daxpay.open.payment.unipay.trade.service; + +import cn.daxpay.open.payment.trade.order.entity.RefundOrder; +import cn.daxpay.open.payment.trade.runtime.service.refund.RefundService; +import cn.daxpay.open.payment.unipay.param.trade.refund.RefundParam; +import cn.daxpay.open.payment.unipay.result.trade.refund.RefundResult; +import cn.daxpay.open.payment.unipay.trade.convert.UnipayRefundOrderConvert; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/// # 退款发起服务(对外) +/// +/// 对外统一退款的编排入口: 对外签名入参 → 内部编排参数, 委托核心 [RefundService] 建单调通道, +/// 再将退款单实体映射为对外响应 [RefundResult]。 +/// 核心层只认内部 [cn.daxpay.open.payment.trade.runtime.param.RefundParam], 对外签名字段(mchNo/appId/sign)在此剥离。 +/// 与退款查询/同步的 [RefundOrderQueryService] / [RefundOrderSyncService] 并列。 +@Service +@RequiredArgsConstructor +public class RefundOrderService { + + private final RefundService refundService; + + /// 发起退款 + public RefundResult refund(RefundParam param) { + RefundOrder refundOrder = refundService.refund(UnipayRefundOrderConvert.CONVERT.toRuntime(param)); + return UnipayRefundOrderConvert.CONVERT.toRefundResult(refundOrder); + } +} diff --git a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/service/RefundOrderSyncService.java b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/service/RefundOrderSyncService.java new file mode 100644 index 000000000..9f706de0c --- /dev/null +++ b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/trade/service/RefundOrderSyncService.java @@ -0,0 +1,59 @@ +package cn.daxpay.open.payment.unipay.trade.service; + +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.payment.trade.order.dao.RefundOrderManager; +import cn.daxpay.open.payment.trade.order.entity.RefundOrder; +import cn.daxpay.open.payment.trade.runtime.service.refund.RefundSyncService; +import cn.daxpay.open.payment.unipay.param.trade.refund.RefundSyncParam; +import cn.daxpay.open.payment.unipay.result.trade.refund.RefundSyncResult; +import cn.hutool.core.util.StrUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Objects; + +/// # 退款订单同步服务(对外) +/// +/// 对外统一入口: 按平台退款号或商户退款号定位退款单, 委托 [RefundSyncService] 查询通道终态回写。 +/// adjust 标记: 同步前后本地状态发生变化即为 true, 让商户感知本次同步是否触发了状态调整。 +/// 与支付同步 [cn.daxpay.open.payment.trade.runtime.service.sync.PaySyncService] 的 adjust 语义对齐。 +@Slf4j +@Service +@RequiredArgsConstructor +public class RefundOrderSyncService { + + private final RefundOrderManager refundOrderManager; + private final RefundSyncService refundSyncService; + + /// 退款同步 + public RefundSyncResult sync(RefundSyncParam param) { + // 校验参数, 平台退款号和商户退款号不能都为空 + if (StrUtil.isBlank(param.getRefundNo()) && StrUtil.isBlank(param.getBizRefundNo())) { + // 退款: 退款号不能都为空(复用统一接口层通用单号校验 key) + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.orderNoRequired"); + } + + // 按号定位退款单(优先平台退款号) + RefundOrder refundOrder; + if (StrUtil.isNotBlank(param.getRefundNo())) { + refundOrder = refundOrderManager.findByRefundNo(param.getRefundNo()) + .orElseThrow(() -> new DataNotExistException("pay.error.refund.orderNotFound")); + } else { + refundOrder = refundOrderManager.findByBizRefundNo(param.getBizRefundNo(), param.getAppId()) + .orElseThrow(() -> new DataNotExistException("pay.error.refund.orderNotFound")); + } + + // 记录同步前状态, 同步后比较得出是否调整 + String statusBefore = refundOrder.getStatus(); + RefundOrder synced = refundSyncService.sync(refundOrder); + String statusAfter = synced.getStatus(); + boolean adjust = !Objects.equals(statusBefore, statusAfter); + + return new RefundSyncResult() + .setOrderStatus(statusAfter) + .setAdjust(adjust); + } +}