Kaynağa Gözat

docs(交付): 门店地区交付说明追加第六节 —— 开放 API 下单直传地区码

- 需求缘起:openapi 是服务器对服务器,请求 IP 是调用方机房的 ⇒ 让调用方直传
  GB/T 2260 省市码(用户定案 + 文档格式硬要求)
- 行为口径 6 条:显式门店码优先 / 市码选填且必须属于省 / 市无店回退全省 /
  全省无可用店 400 先于落库与上游 / 种子 = out_biz_no / 只给 city_code 400
- 提交表:cb153add(后端)+ fcf026c7(文档);现量 origin/java 停在 f09e9cce,
  本地领先 3(含本批之前的夹具刀 2717f793),自稳定措辞不写死总数
- 读数:RED 编译错(logs/_tdd-region-red-1.txt)、聚焦 86/0/0/0、五变异两向
  精确红集 + 无害全绿、整包 1232/0/0/0 32.298s
- ★ 新产物指纹取代 §二 旧件:jar 2F51689B… 143,147,907B(开包回读全 True)、
  dist.zip 3cbbdfff… 9,376,009B(281 条目 / 0 反斜杠 / testzip None)
- 悬置如实:收银台腿(createQr)本批未动待拍板;挑店未端到端真签名跑过
alphaH 6 gün önce
ebeveyn
işleme
1e23d0a83f

+ 94 - 0
.claude/plan/2026-10-08-har-shop-region-交付说明.md

@@ -122,3 +122,97 @@
 `BusinessException` **不带 `@ResponseStatus`**(HTTP 200 + body `code`),顾客页自建实例
 读的是 `res.data.msg`;如果哪天有人把它改成真 4xx/5xx,顾客页会 fallback 成
 「网络异常,请检查网络后重试」,那句「你所在地区未开通」就再也到不了顾客眼前。
+
+---
+
+## 六、2026-10-09 追加:开放 API 下单直传地区码(★ 本节产物指纹取代 §二 的,旧件别再用)
+
+**需求(用户原文)**:「还有个点做了没?har的下单开放api,我看文档中没有让调用方传用户ip」
+→ 定案:「要么让调用方传用户地区也可以,省的我们解析了」→ 硬性要求:
+「那就传地区,但是要在开放文档里写清楚地区的格式,要按照我们接收的格式来,
+不然找不到门店就麻烦了」。
+
+**为什么不能按 IP**:openapi 是服务器对服务器调用,请求 IP 是**调用方机房**的,不是顾客的
+(只有收款链接那条腿能按 IP —— 顾客浏览器直连)。所以本次让调用方直传 **GB/T 2260 省市码**
+(不传 IP、平台不解析)。
+
+### 行为口径(实现前已与你确认,未改)
+
+1. 显式 `shop_code` / `third_shop_code` 优先,两个地区字段被忽略(现状不变);
+2. 都没给而给了任一地区码 ⇒ 平台按该地区挑一家,填进 `third_shop_code`
+   (**实体与上游请求两处**),响应回显挑中的那家;
+3. 市码选填:不给按全省挑;给了但**市内无可用店回退全省**(与收款链接腿同一步梯);
+4. **全省无可用店 ⇒ 400 拒收**,先于落库与上游(不留单、不打上游、不发订单信号)——
+   相对「静默退回不带门店下单」,后者等于调用方明确要求的地区归属悄悄落空;
+5. 只给 `city_code` 不给 `province_code` ⇒ 400(省码是地区定位的根);
+6. 同地区多店的稳定散列种子 = **`out_biz_no`**(同一 `out_biz_no` 重复调用挑到同一家)。
+
+### 提交(现量:`origin/java` 停在 `f09e9cce`;本地领先 **3** 个 —— 含本批之前的测试夹具刀 `2717f793`)
+
+| 提交 | 内容 |
+|---|---|
+| `cb153add` | 后端:DTO 两个选填字段;`HarShopService#pickForOrder`(校验 + 挑店 + 三种 400,抽出 `requireKnownProvince` 与建店共用同一句文案);`HarOrderService#create` 接线(挑店块 + builder 带 `thirdShopCode`);新契约测试类 `HarOrderCreateJsonContractTest` |
+| `fcf026c7` | 开放文档 `har-openapi.md` 10.2:字段两行 + 挑店说明块(**格式按接收口径逐字**)+ 响应表 `third_shop_code` 语义订正 + 章首补句 |
+
+### 验证读数(本机实测)
+
+- **RED**(先写测试,`logs/_tdd-region-red-1.txt`):三个测试文件红于编译错 ——
+  `pickForOrder` 不存在(8 处)、`setProvinceCode`/`setCityCode` 不存在、
+  `HarOrderService` 期望 4 参实到 5 参。原因全部是「功能不存在」,无笔误。
+- **GREEN 聚焦**(`logs/_tdd-region-green-1.txt`):**86/0/0/0** =
+  挑店 22(13→22)+ 下单 61(54→61)+ 新契约 3。
+- **变异两向**(5 个真实变异 + 1 个无害改动,全部先确认变异体生效、还原后逐字节回基线):
+
+  | 变异体 | 红集(预测=实测) |
+  |---|---|
+  | 城市码整个忽略 | 4 红(cityTier 挑成 SZ + 三条 400 文案/校验),82 对照绿 |
+  | 显式门店码优先失效 | 2 红(两个 explicit 用例 NPE),84 对照绿 |
+  | 挑不出来不拒收 | 3 红(三条 400 守卫,none thrown),83 对照绿 |
+  | 散列种子换成 enterpriseId | 4 红(四条钉了精确种子的 create 用例),82 对照绿 |
+  | 上游发 dto 的而不是挑中的 | 1 红(「挑中的店必须真的发到上游」NJ→null),85 对照绿 |
+  | 无害重构(守卫提局部变量) | 全绿 86/86 ✓ |
+
+  还原复核:`HarShopService` `6341E642…`、`HarOrderService` `41745D49…`、
+  `HarOrderCreateDTO` `40165A56…`(与变异前一致)。
+- **整包**:**1232 条,0 失败 0 错误 0 跳过,BUILD SUCCESS**,32.298s
+  (`logs/_mvn-full-region-order.txt`;1232 = 上一批 1213 + 本批 19)。
+- **产物**(★ 取代 §二 的指纹;旧 jar `b6b64c8a…` / 旧 dist `80038a66…` 作废):
+  - `java/target/payment-platform-1.0.0.jar`,143,147,907 字节,12:13:28,
+    sha256 `2F51689B49F8B51D5B890973BEDDF30557A553AD04CA732DA5D5BC2B772133D5`。
+    开包回读(`logs/_jar-readback-region-order.py`,只读 jar 里的 .class 常量池):
+    `pickForOrder`、`requireKnownProvince`、四条 400 文案前缀、
+    DTO 的 `provinceCode/cityCode/getProvinceCode/setCityCode` 全为 True。
+  - `frontend/dist.zip`,9,376,009 字节,12:15:03,
+    sha256 `3cbbdfffae3d6638fc662af1985173b685aafa928caa10c3ea2c4796d0c247b6`。
+    281 条目 = 277 文件 + 4 目录条目(与上一版**同形**)、**全正斜杠**、`testzip()` 无坏成员;
+    新文档句(「按地区自动挑店」「就必须一并传 province_code」「该地区没有可用门店」)
+    **只**出现在 `dist/js/index.BRvi1q4X.js` —— 构建产物回读,zip 内也回读到同一文件
+    (`logs/_dist-zip-region-order.py`)。
+- **ts:check 没重跑**:本批只动 md(文档经 `?raw` 导入、类型恒为 string),未新增前端代码 ——
+  与上批「md 改动不进类型检查」的同一条实测口径。
+
+### 开放文档写了什么(你点名要的「格式」)
+
+10.2 请求表加 `province_code`/`city_code` 两行,其后一块说明写死:**与 10.1 建店 GB/T 2260
+同一套码**(省级两位、地级四位);**传代码不是地名**;`city_code` 必须属于 `province_code`;
+**只在没传任何门店码时生效**;先同市、市无店回退全省;同地区多店按 `out_biz_no` 稳定;
+挑中回显在响应 `third_shop_code`;三种 400 文案逐字(`省份编码不存在: 99` /
+`城市编码不属于该省: 3301(省 32)` / `省份编码不能为空: 传了 city_code 就必须一并传 province_code` /
+`该地区没有可用门店: 省 32`(带市码时 `该地区没有可用门店: 3201(省 32)`))——
+**这四个字符串与测试里钉的完全同一批**,文档不是手抄的。
+
+### 部署(与 §三 同序,换新 jar/dist 即可)
+
+1. 先跑 `db/har-shop-region.sql`(若尚未跑;跑过就不必重复);
+2. 换 `2F51689B…` 的 jar + `3cbbdfff…` 的 dist.zip;
+3. 无需新菜单 SQL、无需重登。
+
+### 悬置/未验(如实)
+
+1. **收银台腿(`HarCashierService#createQr`)不带门店码,本批未动** —— 它要不要一并接
+   「地区」或「挑店」,待你拍板(现状=不带,不是回归)。
+2. **「全省无店 ⇒ 400 拒收」按已批准设计执行**(未再单独确认过,若你要改成「允许不带门店
+   直接下单」请说)。
+3. 挑店在**真实调用方**下的表现未端到端跑过(开放 API 需要一条真签名请求);本次只到
+   「单元 + 契约 + 产物回读」这一层。
+4. `pay_url` 真机扫码仍未做过(老账,与 §四.2 相同)。