소스 검색

feat(汇安融): 上游客户端加退款 —— kf.alipay.trade.refund

规范 5.4(表 8):biz 只有 order_no 与 third_order_no,**没有金额字段**
⇒ 本通道是全额退款,上游没给部分退款的入口。响应 detail 的结构上游未公开
(设计文档记「HAR 侧接口仍在开发中,detail 结构未公开」),所以客户端只回传
原始 HarResponse、不解析 detail —— 字段名猜不得。

method 取值取「章标题 + 接口声明表第 4 行 + 表 8 表头行」这三处一致的
kf.alipay.trade.refund,**不取**表 8 备注格里的 kf.jh.stage.trade.refund:
备注格那一列在本规范里不可信,precreate 已由联调实证过(备注格写
kf.jh.stage.trade.precreate,可用的是 kf.alipay.trade.precreate)。

出站用例同时钉住 path 与 bizContent 原文。这台子(JDK 自带 HttpServer)是本仓
唯一会看真实出站 URL 的地方 —— 路径类缺陷在别处完全不可见。

读数:HarClientOutboundUrlTest 3 条全绿(含 query 那条正控)。
alphaH 2 주 전
부모
커밋
4b896a0343

+ 24 - 0
java/src/main/java/com/payment/platform/module/payment/har/client/HarClient.java

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.payment.platform.module.payment.har.client.dto.HarPayRequest;
 import com.payment.platform.module.payment.har.client.dto.HarPrecreateRequest;
 import com.payment.platform.module.payment.har.client.dto.HarQueryRequest;
+import com.payment.platform.module.payment.har.client.dto.HarRefundRequest;
 import com.payment.platform.module.payment.har.client.dto.HarShopCreateRequest;
 import com.payment.platform.module.payment.har.config.HarProperties;
 import lombok.RequiredArgsConstructor;
@@ -47,6 +48,17 @@ public class HarClient {
     private static final String METHOD_PRECREATE = "kf.alipay.trade.precreate";
     private static final String METHOD_PAY = "kf.alipay.trade.pay";
     private static final String METHOD_QUERY = "kf.alipay.trade.query";
+
+    /**
+     * 退款 method。取值见规范 5.4 章标题、接口声明表第 4 行、表 8 表头行 —— 三处一致。
+     *
+     * <p>表 8 里 {@code method} 的**备注格**写的是 {@code kf.jh.stage.trade.refund},
+     * 与 precreate 同款的复制粘贴缺陷(那次备注格写 {@code kf.jh.stage.trade.precreate},
+     * 联调实证可用的是 {@code kf.alipay.trade.precreate})。备注格那一列在本规范里不可信,
+     * 以章标题/声明表/表头为准。
+     */
+    private static final String METHOD_REFUND = "kf.alipay.trade.refund";
+
     private static final String METHOD_SHOP_CREATE = "kf.har.shop.create";
 
     /** 上游 context-path,见 demo Main.java:13 的 http://localhost:9090/kf-alipay-api/... */
@@ -68,6 +80,18 @@ public class HarClient {
         return invoke(METHOD_QUERY, "/api/alipay/trade/query", req);
     }
 
+    /**
+     * 订单退款(全额)。
+     *
+     * <p>规范 5.4 的 biz 只有 order_no / third_order_no,没有金额字段 —— 上游没给部分退款的入口。
+     * 响应的 {@code detail} 结构上游未公开(设计文档记「HAR 侧接口仍在开发中」),
+     * 因此本方法只回传原始 {@link HarResponse},**不解析 detail**:字段名猜不得,
+     * 退款结果的收敛走通知与主动查询。
+     */
+    public HarResponse refund(HarRefundRequest req) {
+        return invoke(METHOD_REFUND, "/api/alipay/trade/refund", req);
+    }
+
     /**
      * 门店创建。
      *

+ 29 - 0
java/src/main/java/com/payment/platform/module/payment/har/client/dto/HarRefundRequest.java

@@ -0,0 +1,29 @@
+package com.payment.platform.module.payment.har.client.dto;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * 订单退款业务参数 — 对应规范 5.4 请求参数(表 8)。
+ *
+ * <p><b>只有这两个字段,没有金额</b> —— 表 8 的 biz 就这两行,所以本通道是**全额退款**,
+ * 部分退款上游没有提供入口。哪天上游加了 amount,改动落在这里。
+ *
+ * <p>表 8 的两个字段「不为空」列都是空的(即都选填),但备注里写了
+ * 「与订单号同时存在时 忽略该属性」—— 所以两个都给也不会错,只是 order_no 说了算。
+ * 我们的口径是优先给 order_no(上游单号),拿不到才退到 third_order_no,同
+ * {@code HarOrderService#syncFromUpstream} 的查询口径。
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class HarRefundRequest {
+
+    /** 汇安融订单号 */
+    @JsonProperty("order_no")
+    private String orderNo;
+
+    /** 第三方订单号(我方 order_no)。与 order_no 同时存在时上游忽略该属性 */
+    @JsonProperty("third_order_no")
+    private String thirdOrderNo;
+}

+ 28 - 0
java/src/test/java/com/payment/platform/module/payment/har/client/HarClientOutboundUrlTest.java

@@ -1,6 +1,7 @@
 package com.payment.platform.module.payment.har.client;
 
 import com.payment.platform.module.payment.har.client.dto.HarQueryRequest;
+import com.payment.platform.module.payment.har.client.dto.HarRefundRequest;
 import com.payment.platform.module.payment.har.client.dto.HarShopCreateRequest;
 import com.payment.platform.module.payment.har.config.HarProperties;
 import com.sun.net.httpserver.HttpExchange;
@@ -107,6 +108,33 @@ class HarClientOutboundUrlTest {
                         + "别把 path 改回去。");
     }
 
+    /**
+     * 退款出站 URL。
+     *
+     * <p>表 8 的 {@code method} 备注格写的是 {@code kf.jh.stage.trade.refund},而**同一张表**
+     * 的表头行、5.4 章标题、接口声明表第 4 行三处都写 {@code kf.alipay.trade.refund}。
+     * 这与 precreate 是同一个复制粘贴缺陷(那次备注格写 {@code kf.jh.stage.trade.precreate},
+     * 而联调实证可用的是 {@code kf.alipay.trade.precreate})—— 备注格那一列在本规范里不可信。
+     *
+     * <p>请求体一并钉住:5.4 的 biz 只有 order_no / third_order_no,**没有金额字段**
+     * ⇒ 本通道只能全额退款。哪天出现 amount 字段,这条断言会提醒我们上游改了契约。
+     */
+    @Test
+    void refund_hitsTheAlipayRefundPath() {
+        HarRefundRequest req = new HarRefundRequest();
+        req.setOrderNo("UP-1");
+
+        new HarClient(props).refund(req);
+
+        assertEquals("/kf-alipay-api/api/alipay/trade/refund", lastPath.get(),
+                "实际收到 = " + lastPath.get());
+        assertEquals("kf.alipay.trade.refund", method(),
+                "表 8 备注格的 kf.jh.stage.trade.refund 是不可信的那一列。"
+                        + "若联调回「method 不存在」,要先去上游更正那张表,别照备注格改。");
+        assertEquals("{\"order_no\":\"UP-1\"}", lastForm.get().get("bizContent"),
+                "business 参数按上游字段名原样发,不做映射层;未设置的字段不出现(NON_NULL)");
+    }
+
     private String method() {
         Map<String, String> form = lastForm.get();
         assertNotNull(form, "台子没收到任何表单 —— 出站请求根本没发出去");