Răsfoiți Sursa

feat(汇安融): 下单补 biz 字段,trade_type 非分期取值改 alipay_un_stage

上游 260924 版规范与 260915 逐格对撞(多重集,与位置无关)实测只有两处改值加一行
新增,其余 978 格逐字未动:

  - 门店 url:/api/shop/create -> /api/merchant/shop/create(已在 e57a82b 落地)
  - trade_type 备注:alipay_payment -> alipay_un_stage
  - 新增 biz 行(7 格):业务类型 / varchar(16) / 备注「非分期传:har_ali_un_stage」

判不出新旧的只有 trade_type 那一行:整行 6 格被划了红删除线(全文唯一一处 w:strike,
20 个 run),而新增的 biz 行是红字不划线。两份文档 w:ins/w:del 都是 0,这是手工格式、
不是修订标记,改的范围看不出来,于是留下两种读法:

  A:trade_type 整行作废,biz 取而代之
  B:trade_type 保留、非分期值改名,另加一个选填 biz

判别只能靠部署后打一发探针:两个都不传,看上游点谁的名(上游确实是逐个点名的,
subject 那次就是「交易主题(subject)不能为空!」)。判出来之前按超集走 —— 两个都发。

落点:
- DTO / 两个 client 请求体 / Entity / VO 各加 biz;Service 三处透传(建单落库、
  主扫 precreate、被扫 pay)。
- biz 是纯透传字段,后端不推导、不校验取值。「非分期传 har_ali_un_stage」是给调用方
  的指示,不是后端要算的东西。
- HarTradeType.ALIPAY_PAYMENT 改名 ALIPAY_UN_STAGE:该常量当前无引用,改它只为别让
  下一个人照旧名去写默认值。

DDL:db/har-order-biz.sql 给 pay_har_order 加 biz 列。
【本脚本尚未执行】—— 本仓的 SQL 一律我写、你执行。

测试 5 条,两向都钉:有值时 bizContent 的键名与取值都对(换个值也跟着走,防写死成
常量);不设时该键不出现(选填 + NON_NULL)。

顺带订正一处我自己的坏引用:规范 docx 里根本没有「表N」这种编号(全文「表」字只出现
2 次,都是「表示」),原先注释里写的「表2 行13」是我编的序号,而且两支脚本还编出了
不同的号。已全部换成按内容可解析的锚点(「订单创建参数表 biz 行」)。

未决(别当已办):读法 A 若成立,HarOrderCreateDTO.tradeType 上的 @NotBlank 会挡住
「调用方想只传 biz」,届时要放开 —— 这一步挂在探针结果上。
另:新规范自己不自洽,alipay_payment 在 pay 参数表、query 参数表、异步通知参数表里
一字未动,仍是 3 处。
alphaH 3 săptămâni în urmă
părinte
comite
4af7935190

+ 4 - 0
java/src/main/java/com/payment/platform/module/payment/har/client/dto/HarPayRequest.java

@@ -34,9 +34,13 @@ public class HarPayRequest {
     @JsonProperty("stage_num")
     private String stageNum;
 
+    /** alipay_stage:花呗分期  alipay_un_stage:支付宝支付(260924 前叫 alipay_payment) */
     @JsonProperty("trade_type")
     private String tradeType;
 
+    /** 业务类型。选填,非分期传 har_ali_un_stage(规范 260924 订单创建参数表 biz 行) */
+    private String biz;
+
     private String percent;
 
     @JsonProperty("merchant_code")

+ 4 - 1
java/src/main/java/com/payment/platform/module/payment/har/client/dto/HarPrecreateRequest.java

@@ -31,10 +31,13 @@ public class HarPrecreateRequest {
     @JsonProperty("stage_num")
     private String stageNum;
 
-    /** alipay_stage:花呗分期  alipay_payment:支付宝支付 */
+    /** alipay_stage:花呗分期  alipay_un_stage:支付宝支付(260924 前叫 alipay_payment) */
     @JsonProperty("trade_type")
     private String tradeType;
 
+    /** 业务类型。选填,非分期传 har_ali_un_stage(规范 260924 订单创建参数表 biz 行) */
+    private String biz;
+
     /** 分期时必填,100:商户承担 0:用户承担 */
     private String percent;
 

+ 13 - 1
java/src/main/java/com/payment/platform/module/payment/har/dto/HarOrderCreateDTO.java

@@ -41,10 +41,22 @@ public class HarOrderCreateDTO {
     @Size(max = 64, message = "商品名称最长 64 位")
     private String title;
 
-    /** alipay_stage:花呗分期  alipay_payment:支付宝支付 */
+    /**
+     * alipay_stage:花呗分期  alipay_un_stage:支付宝支付。
+     *
+     * <p>非分期那个值 260924 之前叫 {@code alipay_payment},规范 260924 改成
+     * {@code alipay_un_stage}。原样透传,后端不校验取值。
+     */
     @NotBlank(message = "交易方式不能为空")
     private String tradeType;
 
+    /**
+     * 业务类型。选填。规范 260924 订单创建参数表新增 biz 行:非分期传 {@code har_ali_un_stage}。
+     *
+     * <p>同样是原样透传 —— 取值规则是给调用方的指示,不是后端要推导的东西。
+     */
+    private String biz;
+
     /** 分期时必填,3/6/12/24 */
     private String stageNum;
 

+ 3 - 0
java/src/main/java/com/payment/platform/module/payment/har/dto/HarOrderVO.java

@@ -25,6 +25,8 @@ public class HarOrderVO {
     private BigDecimal price;
     private String title;
     private String tradeType;
+    /** 业务类型。非分期传 har_ali_un_stage(规范 260924 订单创建参数表 biz 行) */
+    private String biz;
     private String stageNum;
     private String percent;
     private Integer payTimeout;
@@ -58,6 +60,7 @@ public class HarOrderVO {
         vo.setPrice(e.getPrice());
         vo.setTitle(e.getTitle());
         vo.setTradeType(e.getTradeType());
+        vo.setBiz(e.getBiz());
         vo.setStageNum(e.getStageNum());
         vo.setPercent(e.getPercent());
         vo.setPayTimeout(e.getPayTimeout());

+ 4 - 1
java/src/main/java/com/payment/platform/module/payment/har/entity/HarOrderEntity.java

@@ -51,9 +51,12 @@ public class HarOrderEntity extends PaymentEnterpriseBaseEntity {
 
     private String title;
 
-    /** alipay_stage:花呗分期  alipay_payment:支付宝支付 */
+    /** alipay_stage:花呗分期  alipay_un_stage:支付宝支付(260924 前叫 alipay_payment) */
     private String tradeType;
 
+    /** 业务类型。非分期传 har_ali_un_stage(规范 260924 订单创建参数表 biz 行) */
+    private String biz;
+
     private String stageNum;
 
     private String percent;

+ 7 - 2
java/src/main/java/com/payment/platform/module/payment/har/enums/HarTradeType.java

@@ -6,8 +6,13 @@ public final class HarTradeType {
     /** 花呗分期 */
     public static final String ALIPAY_STAGE = "alipay_stage";
 
-    /** 支付宝支付 */
-    public static final String ALIPAY_PAYMENT = "alipay_payment";
+    /**
+     * 支付宝支付。规范 260924 起叫 {@code alipay_un_stage}(此前叫 {@code alipay_payment})。
+     *
+     * <p>常量目前无引用(取值是调用方透传,后端不校验);留着是为了让这里与规范同步,
+     * 别让下一个人照旧名去写默认值。
+     */
+    public static final String ALIPAY_UN_STAGE = "alipay_un_stage";
 
     private HarTradeType() {
     }

+ 3 - 0
java/src/main/java/com/payment/platform/module/payment/har/service/HarOrderService.java

@@ -226,6 +226,7 @@ public class HarOrderService {
         entity.setPrice(dto.getPrice());
         entity.setTitle(dto.getTitle());
         entity.setTradeType(dto.getTradeType());
+        entity.setBiz(dto.getBiz());
         entity.setStageNum(dto.getStageNum());
         entity.setPercent(dto.getPercent());
         entity.setPayTimeout(dto.getPayTimeout());
@@ -442,6 +443,7 @@ public class HarOrderService {
         req.setTitle(dto.getTitle());
         req.setStageNum(dto.getStageNum());
         req.setTradeType(dto.getTradeType());
+        req.setBiz(dto.getBiz());
         req.setPercent(dto.getPercent());
         req.setMerchantCode(merchant.getMerchantCode());
         req.setThirdOrderNo(orderNo);
@@ -462,6 +464,7 @@ public class HarOrderService {
         req.setAuthCode(dto.getAuthCode());
         req.setStageNum(dto.getStageNum());
         req.setTradeType(dto.getTradeType());
+        req.setBiz(dto.getBiz());
         req.setPercent(dto.getPercent());
         req.setMerchantCode(merchant.getMerchantCode());
         req.setThirdOrderNo(orderNo);

+ 29 - 0
java/src/main/resources/db/har-order-biz.sql

@@ -0,0 +1,29 @@
+-- pay_har_order 新增 biz 列(2026-09-24,规范 260924)
+-- 手工执行: psycopg2 / psql 连接 payment_platform_java 库
+--
+-- 【本脚本尚未执行】执行由用户做。
+--
+-- ── 背景 ─────────────────────────────────────────────────────────────
+-- 《支付宝支付接口接入规范260924》订单创建(kf.alipay.trade.precreate)参数表新增了一行:
+--     biz | 业务类型 | varchar(16) | (不为空列留空 ⇒ 选填)| 备注「非分期传:har_ali_un_stage」
+-- 同一张表里原来那行 trade_type 被整行划了红删除线,而新增的 biz 行是红字不划线。
+-- 两种标注格式不同,光读文档判不出 trade_type 是「作废」还是「改了枚举名」,
+-- 所以这一版把 biz 作为**透传字段**一并接上,两个都发:
+--     trade_type=alipay_un_stage(原 alipay_payment)
+--     biz=har_ali_un_stage(非分期)
+-- 哪一半不被上游认,联调读数会直接指出来。
+--
+-- 列宽 16 取自规范;har_ali_un_stage 是 16 字符,正好占满。
+-- 选填 ⇒ 不加 NOT NULL、不给默认值:没传就是 NULL,出网时被 NON_NULL 丢掉。
+--
+-- ── 执行 ─────────────────────────────────────────────────────────────
+ALTER TABLE pay_har_order ADD COLUMN IF NOT EXISTS biz varchar(16);
+
+COMMENT ON COLUMN pay_har_order.biz IS
+    '业务类型(上游 biz)。非分期传 har_ali_un_stage。选填,原样透传不自造';
+
+-- ── 只读核对(执行后跑)──────────────────────────────────────────────
+-- SELECT column_name, data_type, character_maximum_length, is_nullable
+--   FROM information_schema.columns
+--  WHERE table_name = 'pay_har_order' AND column_name = 'biz';
+-- 期望:biz | character varying | 16 | YES

+ 51 - 0
java/src/test/java/com/payment/platform/module/payment/har/client/HarClientBizContentTest.java

@@ -7,6 +7,7 @@ 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.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -86,4 +87,54 @@ class HarClientBizContentTest {
         req.setAuthCode("280812345678901234");
         assertSubjectTracksTitle(req, req::setTitle);
     }
+
+    /**
+     * 断言 {@code biz} 出网键名与取值都对,且跟着 setter 走。
+     *
+     * <p>为什么 {@code trade_type} 与 {@code biz} 两个都发:260924 那份把
+     * {@code trade_type} 整行划了红线(参数名/描述/类型/必填/备注五格全带
+     * {@code <w:strike/>}),另起一行 {@code biz} 却是红字**不划线**。两种标注格式
+     * 不同,光读文档判不出 {@code trade_type} 是「作废」还是「改了枚举名」。
+     * 两个都发在两种读法下都成立(上游容忍多余字段,见类注释),
+     * 哪一半不被认,联调读数会直接指出来。
+     */
+    private void assertBizTracksValue(Object req, java.util.function.Consumer<String> setBiz) throws Exception {
+        setBiz.accept("har_ali_un_stage");
+        JsonNode n = bizContentOf(req);
+        assertTrue(n.has("merchant_code"), "正对照:bizContent 应有 merchant_code,实际=" + n);
+        assertEquals("har_ali_un_stage", n.path("biz").asText(null),
+                "bizContent 缺 biz 或值不对,实际=" + n);
+
+        // 换个值再测一遍:只测一个值时,写死成那个常量同样能绿
+        setBiz.accept("另一个业务类型");
+        assertEquals("另一个业务类型", bizContentOf(req).path("biz").asText(null));
+    }
+
+    @Test
+    void precreate_bizContent_carriesBiz() throws Exception {
+        HarPrecreateRequest req = new HarPrecreateRequest();
+        req.setMerchantCode("M001");
+        assertBizTracksValue(req, req::setBiz);
+    }
+
+    @Test
+    void pay_bizContent_carriesBiz() throws Exception {
+        HarPayRequest req = new HarPayRequest();
+        req.setMerchantCode("M001");
+        req.setAuthCode("280812345678901234");
+        assertBizTracksValue(req, req::setBiz);
+    }
+
+    /**
+     * biz 是选填(260924 订单创建参数表 biz 行「不为空」列是空的):没给就不该出现,
+     * 免得把一个空串当成业务类型送到上游。
+     */
+    @Test
+    void precreate_bizAbsentWhenNotSet() throws Exception {
+        HarPrecreateRequest req = new HarPrecreateRequest();
+        req.setMerchantCode("M001");
+        JsonNode n = bizContentOf(req);
+        assertTrue(n.has("merchant_code"), "正对照:bizContent 应有 merchant_code,实际=" + n);
+        assertFalse(n.has("biz"), "biz 未设置时不应出现在 bizContent 里,实际=" + n);
+    }
 }

+ 41 - 1
java/src/test/java/com/payment/platform/module/payment/har/service/HarOrderServiceTest.java

@@ -57,7 +57,7 @@ class HarOrderServiceTest {
         dto.setOutBizNo("BIZ-0001");
         dto.setPrice(new BigDecimal("12.34"));
         dto.setTitle("测试商品");
-        dto.setTradeType("alipay_payment");
+        dto.setTradeType("alipay_un_stage");
         return dto;
     }
 
@@ -176,6 +176,46 @@ class HarOrderServiceTest {
         verify(harClient, never()).precreate(any());
     }
 
+    /**
+     * {@code biz} 原样送达上游并落库 —— 后端不推导、不改写。
+     *
+     * <p>取值规则(非分期传 {@code har_ali_un_stage},见规范 260924 订单创建参数表 biz 行)
+     * 是给**调用方**的指示,我们只负责送达,与 {@code trade_type} 同一套处理方式。
+     */
+    @Test
+    void create_passesBizThroughToPrecreateAndPersists() {
+        HarOrderCreateDTO dto = dto();
+        dto.setBiz("har_ali_un_stage");
+        when(merchantService.requireByEnterpriseId("E1")).thenReturn(merchant());
+        when(harClient.precreate(any())).thenReturn(response("10000", precreateDetail()));
+
+        service.create(dto, "E1", "KEY1");
+
+        ArgumentCaptor<HarPrecreateRequest> req = ArgumentCaptor.forClass(HarPrecreateRequest.class);
+        verify(harClient).precreate(req.capture());
+        assertEquals("har_ali_un_stage", req.getValue().getBiz());
+
+        ArgumentCaptor<HarOrderEntity> entity = ArgumentCaptor.forClass(HarOrderEntity.class);
+        verify(orderMapper).insert(entity.capture());
+        assertEquals("har_ali_un_stage", entity.getValue().getBiz());
+    }
+
+    /** 被扫(pay)那条腿同样要带上 biz —— 260924 的 pay 表与订单创建表是同一组业务参数(实测逐项相同,只差 auth_code/biz 一项) */
+    @Test
+    void create_scanMode_passesBizThroughToPay() {
+        HarOrderCreateDTO dto = dto();
+        dto.setAuthCode("280812345678901234");
+        dto.setBiz("har_ali_un_stage");
+        when(merchantService.requireByEnterpriseId("E1")).thenReturn(merchant());
+        when(harClient.pay(any())).thenReturn(response("10000", precreateDetail()));
+
+        service.create(dto, "E1", "KEY1");
+
+        ArgumentCaptor<HarPayRequest> req = ArgumentCaptor.forClass(HarPayRequest.class);
+        verify(harClient).pay(req.capture());
+        assertEquals("har_ali_un_stage", req.getValue().getBiz());
+    }
+
     @Test
     void create_upstreamNonSuccessCode_throws() {
         when(merchantService.requireByEnterpriseId("E1")).thenReturn(merchant());