diff --git a/_config/sql/update-tables.sql b/_config/sql/update-tables.sql index 5173d21fd..143273862 100644 --- a/_config/sql/update-tables.sql +++ b/_config/sql/update-tables.sql @@ -145,6 +145,14 @@ COMMENT ON COLUMN "public"."pay_gateway_order"."pay_body" IS '支付参数体( ALTER TABLE "public"."pay_gateway_order" ADD COLUMN IF NOT EXISTS "pay_body_type" varchar(32); COMMENT ON COLUMN "public"."pay_gateway_order"."pay_body_type" IS '支付参数体类型'; +-- 网关容器对齐普通容器: 通道附加参数 +ALTER TABLE "public"."pay_gateway_order" ADD COLUMN IF NOT EXISTS "extra_param" varchar(2048); +COMMENT ON COLUMN "public"."pay_gateway_order"."extra_param" IS '通道附加参数'; + +-- 网关不支持线下终端/被扫: 移除无用列(重构期可破坏性变更) +ALTER TABLE "public"."pay_gateway_order" DROP COLUMN IF EXISTS "terminal_no"; +ALTER TABLE "public"."pay_gateway_order" DROP COLUMN IF EXISTS "bar_code"; + -- 实际上送串索引(回调反查) CREATE INDEX IF NOT EXISTS "idx_pay_trade_relation_order_no" ON "public"."pay_trade" ("relation_order_no"); CREATE INDEX IF NOT EXISTS "idx_pay_normal_order_order_no" ON "public"."pay_normal_order" ("order_no"); diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/convert/GatewayPayOrderConvert.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/convert/GatewayPayOrderConvert.java index 61f87beb6..d98a9809b 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/convert/GatewayPayOrderConvert.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/convert/GatewayPayOrderConvert.java @@ -5,11 +5,15 @@ import cn.daxpay.open.payment.trade.order.result.GatewayPayOrderResult; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; -/// # 网关支付业务单转换 +/// # 网关支付业务单转换(管理) +/// +/// 仅做容器(GatewayPayOrder)到列表 Result 的映射; +/// 详情场景的资金凭证(PayTrade)字段由 Service 层手动补充, 与 [NormalPayOrderConvert] 一致 @Mapper public interface GatewayPayOrderConvert { GatewayPayOrderConvert CONVERT = Mappers.getMapper(GatewayPayOrderConvert.class); + /// GatewayPayOrder → Result (列表用, 不含资金凭证字段) GatewayPayOrderResult toResult(GatewayPayOrder entity); } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/GatewayPayOrder.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/GatewayPayOrder.java index 749c25011..5d795fa21 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/GatewayPayOrder.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/entity/GatewayPayOrder.java @@ -1,6 +1,7 @@ package cn.daxpay.open.payment.trade.order.entity; import cn.daxpay.open.payment.common.entity.MchBaseEntity; +import cn.daxpay.open.payment.merchant.enums.CashierSceneEnum; import cn.daxpay.open.payment.trade.enums.GatewayOrderStatusEnum; import cn.daxpay.open.payment.trade.enums.GatewayPayTypeEnum; import cn.daxpay.open.payment.unipay.param.trade.pay.GoodsDetail; @@ -20,23 +21,24 @@ import java.util.List; /// # 网关支付业务单容器 /// -/// 业务容器统一落在 trade.order(与 NormalPayOrder 同包)。 -/// 聚合扫码/收银台预下单场景: 创建时不知具体通道, 仅承载收款意图。 +/// 业务容器统一落在 trade.order(与 [NormalPayOrder] 同包,纯持久化无编排 service)。 +/// 聚合扫码/收银台预下单场景: 创建时不知具体通道, 仅承载收款意图(bizOrderNo / 标题 / 回调 等); /// 用户真正支付时再创建 pay_trade(trade_type=gateway) 并回填 channel/product/method。 -/// 网关 orderNo 为预下单 URL 号, 与资金 tradeNo 身份分离。 +/// 网关 orderNo 为预下单 URL 号(号段 order()=ORD…), 与资金 tradeNo(PAY…)身份分离; +/// 冗余存储金额/支付/时间线字段, 便于后台查询无需 JOIN pay_trade。 @Data @EqualsAndHashCode(callSuper = true) @Accessors(chain = true) @TableName(value = "pay_gateway_order", autoResultMap = true) public class GatewayPayOrder extends MchBaseEntity { - /// 平台网关业务单号(URL 用, 预下单即生成, 可无 trade) + /// 平台业务单号(容器身份,与 tradeNo 独立生成;预下单即生成、可无 trade;普通通道默认作为上送号 / URL 落地号) private String orderNo; /// 商户业务单号 private String bizOrderNo; - /// 网关类型 + /// 网关支付类型(预下单写入) /// @see GatewayPayTypeEnum private String gatewayType; @@ -50,25 +52,29 @@ public class GatewayPayOrder extends MchBaseEntity { /// @see GatewayOrderStatusEnum private String status; - /// 异步通知地址 + /// 异步通知地址(出站商户通知用) private String notifyUrl; /// 同步跳转地址 private String returnUrl; - /// 商户附加参数 + /// 商户附加参数(回调原样返回) private String attach; - /// 过期时间 + /// 业务单过期时间(超时关单权威,不落 pay_trade) private OffsetDateTime expiredTime; - /// 金额(最小货币单位) + // ===== 金额(冗余,方便查询;预下单即确定,支付前可无 Trade)===== + + /// 业务单金额(最小货币单位) private Long amount; /// 币种 /// @see CurrencyEnum private String currency; + // ===== 支付信息(支付时回填,查询过滤用)===== + /// 支付通道编码(冗余自 product → ProductEnum#getChannel,对应 ChannelEnum;非 PayProviderEnum) /// @see cn.daxpay.open.platform.core.enums.pay.channel.ChannelEnum private String channel; @@ -81,44 +87,41 @@ public class GatewayPayOrder extends MchBaseEntity { /// @see cn.daxpay.open.platform.core.enums.unipay.PayLimitPayEnum private String limitPay; - /// 支付产品 + /// 支付产品编码 /// @see ProductEnum private String product; - /// 支付能力(路由回填) - private String capability; - - /// 通道商户号(路由回填) - private String channelMchNo; - - /// 通道应用 AppId(本笔交易实际使用的微信/通道侧 AppId 快照;解析后写入,关退同步复用) - private String channelAppId; - - // ===== 支付请求参数(支付时写入)===== + // ===== 支付请求参数(支付时写入,审计保留;网关无被扫,不承载 barCode)===== /// 微信 openid(jsapi/app/miniapp) private String openid; - /// 付款码(被扫支付) - private String barCode; - - /// 收银场景 wechat_pay/alipay/union_pay + /// 收银场景(环境识别,支付时回填;与聚合/收银台配置共用同一字典) + /// @see CashierSceneEnum private String scene; - /// 最后发起设备 mobile/pc + /// 最后发起设备(mobile/pc,支付时回填;与 H5 端 window.__DEVICE__ 一致) private String device; + // ===== 时间线(冗余,查询展示用)===== + /// 支付成功时间 private OffsetDateTime payTime; /// 关闭时间 private OffsetDateTime closeTime; - /// 下单客户端 IP;关单/同步/退款透传通道的单一事实源,兼审计排查 - private String clientIp; + // ===== 通道路由(同步时用于解析通道应用凭证;支付路由后回填)===== - /// 终端设备编码 - private String terminalNo; + /// 通道商户号(路由回填) + private String channelMchNo; + + /// 支付能力编码(路由回填) + /// @see cn.daxpay.open.platform.core.enums.pay.channel.PayCapabilityEnum + private String capability; + + /// 通道应用 AppId(本笔交易实际使用的微信/通道侧 AppId 快照;解析后写入,关退同步复用) + private String channelAppId; // ===== 通道回执(支付成功/同步后写入)===== @@ -126,7 +129,7 @@ public class GatewayPayOrder extends MchBaseEntity { /// @see cn.daxpay.open.platform.core.enums.pay.channel.PayProviderEnum private String provider; - /// 付款用户 ID(支付宝 buyer_user_id 等) + /// 付款用户标识(支付宝 user_id、微信 openid 等,非通道 AppId) private String buyerId; /// 通道方记录的支付产品 @@ -141,29 +144,37 @@ public class GatewayPayOrder extends MchBaseEntity { /// 活动类型 private String promotionType; - /// 支付参数体(已拉起缓存,仅落容器) + /// 支付参数体(如微信 prepay_id 组装串,非空表示已拉起支付,免重复请求通道;仅落容器) private String payBody; - /// 支付参数体类型 + /// 支付参数体类型(jsapi/sdk/app) private String payBodyType; - // ===== 通道关联订单号(部分通道专用)===== + // ===== 关联订单号 ===== /// 透传订单号(三方通道产生的透传订单号) private String transOrderNo; /// 实际上送通道的商户订单号(展示冗余;反查权威在 pay_trade.relation_order_no) + /// 普通通道与 orderNo 一致;特殊通道为变形号 private String relationOrderNo; + // ===== 请求信息(关单/同步/退款透传依赖 + 审计)===== + + /// 通道附加参数 + private String extraParam; /// 应用号 @TableField(updateStrategy = FieldStrategy.NEVER) private String appId; - /// 商品明细 + /// 订单商品明细列表(jsonb 存储) @TableField(typeHandler = JacksonTypeHandler.class) private List goodsDetail; + /// 下单客户端 IP;关单/同步/退款透传通道的单一事实源,兼审计排查 + private String clientIp; + /// 错误信息 @TableField(updateStrategy = FieldStrategy.ALWAYS) private String errorMsg; diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/GatewayPayOrderQuery.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/GatewayPayOrderQuery.java index 6966ca722..195edce54 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/GatewayPayOrderQuery.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/param/GatewayPayOrderQuery.java @@ -8,6 +8,8 @@ import lombok.experimental.Accessors; import java.time.OffsetDateTime; /// # 网关支付业务单查询参数(管理) +/// +/// 查询维度与 [NormalPayOrderQuery] 对齐, 另含网关类型 gatewayType / 平台单号 orderNo。 @Data @Accessors(chain = true) @Schema(title = "网关支付业务单查询参数") @@ -29,10 +31,16 @@ public class GatewayPayOrderQuery { @Schema(description = "商户业务单号") private String bizOrderNo; + @QueryParam(type = QueryParam.CompareTypeEnum.LIKE) + @Schema(description = "订单标题") + private String title; + + /// @see cn.daxpay.open.payment.trade.enums.GatewayPayTypeEnum @QueryParam(type = QueryParam.CompareTypeEnum.EQ) @Schema(description = "网关类型") private String gatewayType; + /// @see cn.daxpay.open.payment.trade.enums.GatewayOrderStatusEnum @QueryParam(type = QueryParam.CompareTypeEnum.EQ) @Schema(description = "业务状态") private String status; @@ -41,6 +49,12 @@ public class GatewayPayOrderQuery { @Schema(description = "支付通道") private String channel; + /// @see cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum + @QueryParam(type = QueryParam.CompareTypeEnum.EQ) + @Schema(description = "支付方式") + private String method; + + /// @see cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum @QueryParam(type = QueryParam.CompareTypeEnum.EQ) @Schema(description = "支付产品") private String product; @@ -52,4 +66,12 @@ public class GatewayPayOrderQuery { @QueryParam(type = QueryParam.CompareTypeEnum.LE, fieldName = "create_time") @Schema(description = "创建时间-结束") private OffsetDateTime createTimeEnd; + + @QueryParam(type = QueryParam.CompareTypeEnum.GE, fieldName = "amount") + @Schema(description = "金额下限(分)") + private Long amountMin; + + @QueryParam(type = QueryParam.CompareTypeEnum.LE, fieldName = "amount") + @Schema(description = "金额上限(分)") + private Long amountMax; } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/GatewayPayOrderResult.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/GatewayPayOrderResult.java index 54cf3dc4a..e53289351 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/GatewayPayOrderResult.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/order/result/GatewayPayOrderResult.java @@ -9,12 +9,18 @@ import lombok.experimental.Accessors; import java.time.OffsetDateTime; /// # 网关支付业务单(管理) +/// +/// 列表场景仅填充容器(GatewayPayOrder)字段; +/// 详情场景额外填充资金凭证(PayTrade)字段: tradeNo / outOrderNo / fundStatus 等。 +/// 字段契约与 [NormalPayOrderResult] 对齐, 另含网关独有 gatewayType / scene / device。 @EqualsAndHashCode(callSuper = true) @Data @Accessors(chain = true) @Schema(title = "网关支付业务单") public class GatewayPayOrderResult extends BaseResult { + // ===== 容器(业务单)字段 ===== + @Schema(description = "商户号") private String mchNo; @@ -27,6 +33,7 @@ public class GatewayPayOrderResult extends BaseResult { @Schema(description = "商户业务单号") private String bizOrderNo; + /// @see cn.daxpay.open.payment.trade.enums.GatewayPayTypeEnum @Schema(description = "网关类型") private String gatewayType; @@ -36,39 +43,10 @@ public class GatewayPayOrderResult extends BaseResult { @Schema(description = "描述") private String description; + /// @see cn.daxpay.open.payment.trade.enums.GatewayOrderStatusEnum @Schema(description = "业务状态") private String status; - @Schema(description = "金额(分)") - private Long amount; - - @Schema(description = "币种") - private String currency; - - @Schema(description = "过期时间") - private OffsetDateTime expiredTime; - - @Schema(description = "支付通道") - private String channel; - - @Schema(description = "支付方式") - private String method; - - @Schema(description = "支付产品") - private String product; - - @Schema(description = "收银场景") - private String scene; - - @Schema(description = "设备") - private String device; - - @Schema(description = "支付成功时间") - private OffsetDateTime payTime; - - @Schema(description = "关闭时间") - private OffsetDateTime closeTime; - @Schema(description = "异步通知地址") private String notifyUrl; @@ -78,19 +56,103 @@ public class GatewayPayOrderResult extends BaseResult { @Schema(description = "商户附加参数") private String attach; - // ===== Trade 联表 ===== + @Schema(description = "过期时间") + private OffsetDateTime expiredTime; + + @Schema(description = "金额(分)") + private Long amount; + + @Schema(description = "币种") + private String currency; + + @Schema(description = "支付通道") + private String channel; + + @Schema(description = "支付方式") + private String method; + + @Schema(description = "支付产品") + private String product; + + @Schema(description = "限制支付类型") + private String limitPay; + + /// @see cn.daxpay.open.payment.merchant.enums.CashierSceneEnum + @Schema(description = "收银场景(环境识别)") + private String scene; + + @Schema(description = "设备(mobile/pc)") + private String device; + + @Schema(description = "支付成功时间") + private OffsetDateTime payTime; + + @Schema(description = "关闭时间") + private OffsetDateTime closeTime; + + @Schema(description = "通道商户号") + private String channelMchNo; + + @Schema(description = "支付能力编码") + private String capability; + + @Schema(description = "通道应用AppId") + private String channelAppId; + + @Schema(description = "客户端IP") + private String clientIp; + + @Schema(description = "通道附加参数") + private String extraParam; + + // ===== 资金凭证(PayTrade)联表字段, 仅详情时填充 ===== + @Schema(description = "资金交易号") private String tradeNo; @Schema(description = "通道订单号") private String outOrderNo; + /// @see cn.daxpay.open.payment.trade.enums.PayFundStatusEnum @Schema(description = "资金状态") private String fundStatus; - @Schema(description = "可退金额") + @Schema(description = "可退金额(分)") private Long refundableBalance; + @Schema(description = "支付参数体") + private String payBody; + + @Schema(description = "支付参数体类型") + private String payBodyType; + + @Schema(description = "付款用户ID") + private String buyerId; + + @Schema(description = "微信openid") + private String openid; + + @Schema(description = "支付渠道(厂商)") + private String provider; + + @Schema(description = "通道方记录的支付产品") + private String tradeProduct; + + @Schema(description = "通道方记录的交易方式") + private String tradeWay; + + @Schema(description = "银行卡类型") + private String bankType; + + @Schema(description = "活动类型") + private String promotionType; + + @Schema(description = "透传订单号") + private String transOrderNo; + + @Schema(description = "实际上送通道的商户订单号") + private String relationOrderNo; + @Schema(description = "错误信息") private String errorMsg; } diff --git a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/param/AggregateQrPayParam.java b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/param/AggregateQrPayParam.java index 4c2a952da..68007973d 100644 --- a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/param/AggregateQrPayParam.java +++ b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/param/AggregateQrPayParam.java @@ -16,8 +16,9 @@ public class AggregateQrPayParam { @Size(max = 64, message = "{validation.field.orderNo.size}") private String orderNo; + /// 收银场景(环境识别,与聚合/收银台配置共用 [CashierSceneEnum]) /// @see CashierSceneEnum - @Schema(description = "收银场景 wechat_pay/alipay/union_pay") + @Schema(description = "收银场景(CashierSceneEnum: wechat_pay/alipay/union_pay/douyin/browser)") @NotBlank(message = "{validation.field.scene.notBlank}") @Size(max = 32, message = "{validation.field.scene.size}") private String scene; @@ -26,7 +27,7 @@ public class AggregateQrPayParam { @Size(max = 128, message = "{validation.field.openId.size}") private String openId; - @Schema(description = "设备 mobile/pc") + @Schema(description = "设备(mobile/pc, 与 H5 __DEVICE__ 一致)") @Size(max = 16, message = "{validation.field.device.size}") private String device; diff --git a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/param/GatewayPrePayParam.java b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/param/GatewayPrePayParam.java index a592af181..d4c03228b 100644 --- a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/param/GatewayPrePayParam.java +++ b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/param/GatewayPrePayParam.java @@ -3,9 +3,7 @@ package cn.daxpay.open.payment.unipay.gateway.param; import cn.daxpay.open.payment.trade.enums.GatewayPayTypeEnum; import cn.daxpay.open.payment.unipay.param.MerchantPaymentCommonParam; import cn.daxpay.open.payment.unipay.param.trade.pay.GoodsDetail; -import cn.daxpay.open.payment.unipay.param.trade.pay.TerminalInfo; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.Valid; import jakarta.validation.constraints.*; import lombok.Data; import lombok.EqualsAndHashCode; @@ -57,13 +55,14 @@ public class GatewayPrePayParam extends MerchantPaymentCommonParam { @Size(max = 512, message = "{validation.field.attach.size}") private String attach; + /// 支付扩展参数(JSON 格式,通道特有的长尾参数;与 [NormalPayParam#extraParam] 对齐) + @Schema(description = "支付扩展参数") + @Size(max = 2048, message = "{validation.field.extraParam.size}") + private String extraParam; + @Schema(description = "过期时间") private OffsetDateTime expiredTime; - @Valid - @Schema(description = "终端信息") - private TerminalInfo terminal; - @Schema(description = "商品明细") private List goodsDetail; } diff --git a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/service/GatewayPayAssistService.java b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/service/GatewayPayAssistService.java index 4fd4952c0..fcb1bb4ec 100644 --- a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/service/GatewayPayAssistService.java +++ b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/service/GatewayPayAssistService.java @@ -88,19 +88,20 @@ public class GatewayPayAssistService { if (Objects.equals(status, GatewayOrderStatusEnum.PAID.getCode())) { throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.pay.alreadySuccess"); } - if (List.of(GatewayOrderStatusEnum.CLOSED.getCode(), GatewayOrderStatusEnum.EXPIRED.getCode()) - .contains(status)) { + // failed 与 closed/expired 同为终态, 不允许再返回原 URL 假装可付 + if (List.of(GatewayOrderStatusEnum.CLOSED.getCode(), GatewayOrderStatusEnum.EXPIRED.getCode(), + GatewayOrderStatusEnum.FAILED.getCode()).contains(status)) { throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.pay.failedOrClosed"); } return this.buildPrePayResult(order); } OffsetDateTime expiredTime = payAssistService.getExpiredTime(param.getExpiredTime()); - String terminalNo = param.getTerminal() != null ? param.getTerminal().getTerminalNo() : null; GatewayPayOrder order = new GatewayPayOrder(); order.setAppId(param.getAppId()); - order.setOrderNo(TradeNoGenerateUtil.pay()); + // 容器业务单号用 order() 号段(ORD…), 与资金 tradeNo 的 pay() 号段(PAY…)身份分离 + order.setOrderNo(TradeNoGenerateUtil.order()); order.setBizOrderNo(param.getBizOrderNo()); order.setGatewayType(typeEnum.getCode()); order.setTitle(param.getTitle()); @@ -109,11 +110,11 @@ public class GatewayPayAssistService { order.setNotifyUrl(param.getNotifyUrl()); order.setReturnUrl(param.getReturnUrl()); order.setAttach(param.getAttach()); + order.setExtraParam(param.getExtraParam()); order.setExpiredTime(expiredTime); order.setAmount(param.getAmount()); order.setCurrency(CurrencyEnum.CNY.getCode()); order.setClientIp(param.getClientIp()); - order.setTerminalNo(terminalNo); order.setGoodsDetail(param.getGoodsDetail()); gatewayPayOrderManager.save(order); @@ -140,8 +141,9 @@ public class GatewayPayAssistService { if (Objects.equals(status, GatewayOrderStatusEnum.PAID.getCode())) { throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.pay.alreadySuccess"); } - if (List.of(GatewayOrderStatusEnum.CLOSED.getCode(), GatewayOrderStatusEnum.EXPIRED.getCode()) - .contains(status)) { + // 与 NormalPayOrder 终态校验对齐: failed / closed / expired 均不可继续支付 + if (List.of(GatewayOrderStatusEnum.CLOSED.getCode(), GatewayOrderStatusEnum.EXPIRED.getCode(), + GatewayOrderStatusEnum.FAILED.getCode()).contains(status)) { throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "pay.error.pay.failedOrClosed"); } if (Objects.nonNull(order.getExpiredTime()) diff --git a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/service/GatewayPayHandleService.java b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/service/GatewayPayHandleService.java index d1dee6c82..7c18c0aaa 100644 --- a/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/service/GatewayPayHandleService.java +++ b/daxpay-payment/daxpay-payment-unipay/src/main/java/cn/daxpay/open/payment/unipay/gateway/service/GatewayPayHandleService.java @@ -157,8 +157,9 @@ public class GatewayPayHandleService { order.setLimitPay(payParam.getLimitPay() != null ? String.join(",", payParam.getLimitPay()) : null); order.setOpenid(payParam.getOpenId()); - order.setBarCode(payParam.getAuthCode()); order.setProduct(payParam.getProduct()); + // 与 Normal 对齐: 容器冗余 relationOrderNo, 供 ContainerFieldResolver / 管理展示 + order.setRelationOrderNo(order.getOrderNo()); gatewayPayOrderManager.updateById(order); return trade; } @@ -170,9 +171,11 @@ public class GatewayPayHandleService { trade.setPayTime(result.getFinishTime()); } trade.setOutOrderNo(result.getOutOrderNo()); - // 回执与 payBody 写容器, 由 payAfterHandel 统一处理 + // 回执与 payBody 写容器, 由 payAfterHandel 统一处理(内部 reload 容器) payUniHandleService.payAfterHandel(trade, result); - return this.buildResult(order, trade); + // 必须 reload: payAfterHandel 写的是另一份实体, 入参 order 无 payBody + GatewayPayOrder latest = gatewayPayOrderManager.findById(order.getId()).orElse(order); + return this.buildResult(latest, trade); } private void fillRouteOnOrder(GatewayPayOrder order, NormalPayParam payParam, String scene, String device) { @@ -211,6 +214,8 @@ public class GatewayPayHandleService { payParam.setNotifyUrl(order.getNotifyUrl()); payParam.setReturnUrl(order.getReturnUrl()); payParam.setAttach(order.getAttach()); + // 预下单写入的通道扩展参数, 与 Normal 容器透传一致 + payParam.setExtraParam(order.getExtraParam()); payParam.setExpiredTime(order.getExpiredTime()); payParam.setClientIp(StrUtil.blankToDefault(clientIp, order.getClientIp())); payParam.setGoodsDetail(order.getGoodsDetail());