|
|
@@ -0,0 +1,89 @@
|
|
|
+package com.payment.platform.module.payment.har.client;
|
|
|
+
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
+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 org.junit.jupiter.api.Test;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 钉住下单/收款 bizContent 里的<b>交易主题字段名</b>:必须带 {@code subject}。
|
|
|
+ *
|
|
|
+ * <p>为什么值得一个测试:规范 docx(《支付宝支付接口接入规范260915》)里这个字段叫
|
|
|
+ * {@code title}(商品名称),全篇 0 次出现 {@code subject}。但<b>线上上游不认 {@code title}</b> ——
|
|
|
+ * 2026-09-24 对 {@code https://qcsj88888.com/api/v1/payment/openapi/har/order/create} 实测,
|
|
|
+ * 发出的 bizContent 里 {@code title} 非空,上游仍回:
|
|
|
+ *
|
|
|
+ * <pre>下单失败: 详细参数校验异常,交易主题(subject)不能为空!</pre>
|
|
|
+ *
|
|
|
+ * <p>换成纯 ASCII 的 title({@code HAR-TEST-ITEM})复测,报文一字不变 ⇒ 不是编码问题,
|
|
|
+ * 是<b>字段名对不上</b>。结论:docx 与线上上游不是同一版,以线上为准。
|
|
|
+ *
|
|
|
+ * <p>为什么两个都发而不是把 {@code title} 改名成 {@code subject}:上游<b>成功反序列化</b>了
|
|
|
+ * 带 {@code title} 的报文(否则报的会是解析失败,而不是校验失败),说明多余字段被容忍;
|
|
|
+ * 那么「两个都发」在「上游只认 subject」和「上游两个都认」两种情况下都成立。
|
|
|
+ *
|
|
|
+ * <p>断言走 {@link HarClient#toBizContent(Object)} 本体,而不是另起一个 ObjectMapper:
|
|
|
+ * 生产这支用的是裸 {@code new ObjectMapper()}({@link HarClient} 里的 MAPPER),
|
|
|
+ * 与容器里那个 SNAKE_CASE 的 {@code @Primary} mapper 不是同一支 —— 拿后者当量具,
|
|
|
+ * 测的是量具不是被测物。
|
|
|
+ */
|
|
|
+class HarClientBizContentTest {
|
|
|
+
|
|
|
+ /** 与 {@link HarClient#toBizContent(Object)} 同一支量具,仅用于把结果读回来断言。 */
|
|
|
+ private static final ObjectMapper OM = new ObjectMapper();
|
|
|
+
|
|
|
+ private static final String TITLE = "联调测试商品";
|
|
|
+
|
|
|
+ private JsonNode bizContentOf(Object req) throws Exception {
|
|
|
+ return OM.readTree(HarClient.toBizContent(req));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 断言 {@code subject} 存在、且跟着 {@code title} 走。
|
|
|
+ *
|
|
|
+ * <p>用两个不同的值各测一遍,是为了把「照抄一个常量」这种假修复也照出来 ——
|
|
|
+ * 只测一个值时,写死成那个值同样能绿。
|
|
|
+ */
|
|
|
+ private void assertSubjectTracksTitle(Object req, java.util.function.Consumer<String> setTitle) throws Exception {
|
|
|
+ setTitle.accept(TITLE);
|
|
|
+ JsonNode n = bizContentOf(req);
|
|
|
+ assertSubject(n, TITLE);
|
|
|
+
|
|
|
+ setTitle.accept("另一个标题");
|
|
|
+ assertSubject(bizContentOf(req), "另一个标题");
|
|
|
+ }
|
|
|
+
|
|
|
+ private void assertSubject(JsonNode n, String expected) {
|
|
|
+ // 正对照:先证明这串 JSON 真的序列化出了字段。否则「序列化出空对象」时,
|
|
|
+ // 下面那条也只会报 subject 缺失,把方向指到错误的地方。
|
|
|
+ assertTrue(n.has("merchant_code"), "正对照:bizContent 应有 merchant_code,实际=" + n);
|
|
|
+
|
|
|
+ JsonNode subject = n.get("subject");
|
|
|
+ assertNotNull(subject,
|
|
|
+ "bizContent 缺 subject —— 上游会回「交易主题(subject)不能为空」,bizContent=" + n);
|
|
|
+ assertEquals(expected, subject.asText());
|
|
|
+
|
|
|
+ // 规范 docx 写的是 title,一并保留(取舍见类注释)。
|
|
|
+ assertEquals(expected, n.path("title").asText(null), "title 应仍在(docx 契约)");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void precreate_bizContent_carriesSubject() throws Exception {
|
|
|
+ HarPrecreateRequest req = new HarPrecreateRequest();
|
|
|
+ req.setMerchantCode("M001");
|
|
|
+ assertSubjectTracksTitle(req, req::setTitle);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void pay_bizContent_carriesSubject() throws Exception {
|
|
|
+ HarPayRequest req = new HarPayRequest();
|
|
|
+ req.setMerchantCode("M001");
|
|
|
+ req.setAuthCode("280812345678901234");
|
|
|
+ assertSubjectTracksTitle(req, req::setTitle);
|
|
|
+ }
|
|
|
+}
|