Przeglądaj źródła

docs(收款工作台): 三条裁定落地 + 订正两处我自己的错

用户 2026-09-22 三条裁定:
- 渠道不符不用管:channel 降级为纯标记,收银台逻辑一行不读
- 收银台域名与 notify-url 同源
- 开放接口不改:原 codeUrl -> cashierUrl 提案作废,§0.6 冲突消解

同时订正我自己的两处错:
- 把「支付宝侧用户标识字段名未知」列为待确认,是我读漏了 getauth2 那页——
  原文写明两个渠道回的是同一个 openid(微信 openid / 支付宝 userid),
  且正是 jspay 入参 open_id 要的东西。不是未知项。
- §4.2 里「微信扫不了支付宝的码」被我当成技术事实。改为:两种码内容
  都是我们自己的 URL,该说法未必成立;但结论不受影响,因为分流按 UA 做。
  同时把「实测」与「由实测推出的推断」分开标注。

新增 §11.2 联调待取证清单,把文档里所有推断项集中列出。未实测项包括:
真机走通完整一单、支付宝侧回跳与唤起、上游拼 ?openid= 还是 &openid=、
h5pay 串进收银台链路、正式环境 redirect_uri 白名单。
alphaH 3 tygodni temu
rodzic
commit
10f611ecbe

+ 55 - 20
docs/superpowers/specs/2026-09-22-receive-workbench-design.md

@@ -26,6 +26,12 @@
 
    本条的实测可行性见 §2.4,落地设计见 §4。**§4 已按本条重写**;第 1 条中「返回收银台页面链接」的结论仍成立,但「一单一码、商户先输金额、收银台只做展示」不成立。
 
+   用户就本条追加的三条裁定(2026-09-22):
+   - **渠道不符不用管**:「微信扫不了支付宝的码,反过来也是一样,不用管」⇒ 不做硬拦、不做提示(落点 §4.2)
+   - **收银台域名与 `notify-url` 同源**(落点 §5.3)
+   - **开放接口不改**:「一般人不会抓包,就算抓包也抓不到什么有用的信息」⇒ 原「`codeUrl` → `cashierUrl`」提案作废(落点 §4.7)
+   - **金额下限 `0.01` 元**(落点 §5.3);**上限仍缺一个数**,见 §11.1 第 6 条
+
 ---
 
 ## 1. 批次划分
@@ -125,9 +131,12 @@
 
 **仍待上游确认**(外部依赖,不阻塞本地开发,测试环境已证明不校验):
 1. `redirect_uri` 在**正式环境**是否需要预先登记白名单(测试环境实测未校验);
-2. 支付宝回跳时携带用户标识的**字段名**(文档只写了微信侧示例的 `openid`,支付宝侧未知,联调时实测);
+2. ~~支付宝回跳时携带用户标识的**字段名**~~ → **文档已写明,不是未知项**。见下方订正。
 3. 正式环境参数(域名 / 机构号 / 密钥)。
 
+> **订正(2026-09-22)**:本文档此前把「支付宝侧用户标识字段名未知」列为待确认项,**是我读漏了**。`jsapi/getauth2` 那一页(page 196236540)的返回参数表明写:「`openid` | string | 否 | **微信公众号的openid或支付宝服务窗的userid**」——**两个渠道回跳用的都是同一个参数名 `openid`**,页面按 UA 走哪条腿对它透明。而这个值正是 `v3/jspay` 入参 `open_id` 要的东西:「用户标识(**微信openid/支付宝userid**)」。两页对上,无需再问上游。
+> 剩下真正没验的只有一件事:**真机走一遍**(§11.2)。
+
 ---
 
 ## 3. 批次一:乐收赢去企业化
@@ -201,7 +210,13 @@ DDL(唯一一处,见 §7):
 
 约束:`code_no` 全局唯一;`(tenant_id, name)` 唯一(同名无法管理)。
 
-⚠ **`channel` 是「这个码发给谁」的标记,不是服务端的硬闸。** 真实走哪条通道**以 UA 为准**(§4.4):微信码被支付宝扫到时,仍然让顾客用支付宝付得成,只是页面上给一句提示。理由:顾客扫了码就是要付钱,拦下来对商户是损失。**这条是我的默认取值,用户可推翻**——若要改成硬拦(「本码为微信收款码,请在微信中打开」),改动只在收银台页一处。
+⚠ **`channel` 是「这个码发给谁」的标记,不参与任何服务端判断。** 真实走哪条通道**只以 UA 为准**(§4.4)。
+
+用户 2026-09-22 裁定:「微信扫不了支付宝的码,反过来也是一样,**不用管**」⇒ 不做硬拦,**也不做「渠道不符」的提示**。`channel` 只用于管理端列表的区分与筛选,**收银台逻辑里一行都不读它**。
+
+技术备注(不影响上面的裁定,也不改变任何实现):**我们的二维码内容是自己的 URL**(§4.5),两种码长得一模一样,区别只在管理端那个 `channel` 标签。所以「微信扫不了支付宝的码」在技术上未必成立。但**成立与否结论都一样**——因为分流是按 UA 做的(§4.4):**实测**同一个 `getauth2` 端点,微信 UA 与支付宝 UA **都会**被静默授权,只有其他 UA 才报错。真扫到了,顾客仍会被带到能付成的那条通道。
+
+⚠ 分清上面两句的性质:「两个渠道都静默授权」是**实测**;「所以顾客一定能付成」是我由实测推出的**推断**,没在真机走过完整一单。这是**联调时要验的项**,不是已证事实。
 
 ### 4.3 免鉴权收银台页
 
@@ -246,6 +261,9 @@ GET /payment/cashier/{codeNo}/auth
        支付宝 → openauth.alipay.com  scope=auth_base (静默)
    ▼
 上游回跳 → 收银台?openid=…
+   │  ⚠ 两个渠道回的都是同名参数 `openid`(文档明写:微信 openid / 支付宝 userid),
+   │     所以这里不需要按渠道分支。但**拼接成 `?openid=` 还是 `&openid=` 没实测过**,
+   │     `/auth` 侧解析要容错(§11.2 第 3 条)
    │ 页面捕获 → 存 sessionStorage → history.replaceState 抹掉
    ▼
 顾客输入金额 → 点「确认支付」→ POST ./{codeNo}/pay {金额, 授权标识}
@@ -292,18 +310,15 @@ GET /payment/cashier/{codeNo}/auth
 
 被扫下单:入参 `authCode`(付款码)+ `amount`。走既有 `LsyOrderService.createBarcodeOrder`。
 
-### 4.7 开放接口变更(**与 §0.6 冲突,待用户裁决,默认不动**)
-
-`POST /payment/openapi/pay/qrcode/create` 的应答:`codeUrl` 字段**换成 `cashierUrl`**。
+### 4.7 开放接口**不改**(原「codeUrl → cashierUrl」提案,已裁定废弃)
 
-理由有两条,都需要:
+用户 2026-09-22 裁定:**不改**。理由逐字:「一般人不会抓包,就算抓包也抓不到什么有用的信息」。
 
-1. 商户拿到的应该是**我们的**链接,点开是**我们的**收银台页。直接把 `code_url` 发出去,商户点开看到的是上游域名,等于暴露上游。
-2. 顺带收窄暴露面:`code_url` 是上游可枚举短链,不再外发。
+⇒ `POST /payment/openapi/pay/qrcode/create` 的应答**保持原样**,`codeUrl` 不动。§0.6「原有的开放接口不变」生效,与本节原先的提议之间的冲突就此消解。
 
-这是**破坏性变更**。成立的前提是用户确认过「乐收赢所有代码都没有上过生产」。落地前要再核一次 `LsyPayResultVO.codeUrl` 有没有别的消费方。
+**同时作废的**:原提案里「`code_url` 不出现在任何面向商户或前端的应答里」这条要求(§8 安全约束第 4 条)**随之作废** —— 老开放接口会照常回它。但**新链路(收银台 / 收款码)我们自己的输出里仍然不出现上游域名与字段名**,这条不受影响。
 
-⚠ **冲突**:用户口径 §0.6 是「原有的开放接口**不变**」,本节的提议是**改**它。两条都是用户说的,我不能替用户选。**默认取值:不动**(遵守 §0.6),**代价**是走老开放接口的商户仍会拿到上游链接。等用户裁决。
+**仍然保留的判断**:`code_url` 是上游可枚举短链。用户已评估为低风险(「抓包也抓不到什么有用的信息」),记录在案,不再作为待办。
 
 ### 4.8 独立前端工程 `frontend-receive/`
 
@@ -359,9 +374,9 @@ GET /payment/cashier/{codeNo}/auth
 
 | 键 | 用途 |
 |---|---|
-| `leshouying.cashier-base-url` | 拼收银台地址(二维码内容 + 授权回跳)用。默认取现有对外域名,与 `notify-url` 同源 |
-| `receive.amount-min` | 顾客可输的最小金额,默认 `1`(分,即 0.01 元) |
-| `receive.amount-max` | 顾客可输的最大金额,默认待用户给数(见 §11.1 第 6 条) |
+| `leshouying.cashier-base-url` | 拼收银台地址(二维码内容 + 授权回跳)用。**用户 2026-09-22 裁定:与 `notify-url` 同源**(`https://qcsj88888.com`) |
+| `receive.amount-min` | 顾客可输的最小金额,**用户 2026-09-22 裁定:`0.01` 元**(`1` 分) |
+| `receive.amount-max` | 顾客可输的最大金额,**待用户给数**(见 §11.1 第 6 条) |
 
 不新增:二维码尺寸、颜色、容错级别。
 
@@ -508,7 +523,7 @@ CREATE INDEX        IF NOT EXISTS idx_receive_code_mch ON pay_receive_code (merc
 1. **验签绝不跳过**。进件接口同样是签名请求,一样走既有签名逻辑,不开任何旁路。
 2. **收银台页免鉴权,但不免令牌校验**。无令牌 / 错令牌 → 明确的「链接无效」页。令牌 32 位随机。
 3. **`/payment/cashier/**` 进白名单时逐条精确匹配**,不用 `/**` 粗放放行——本仓既有过滤器注释专门警告过这个坑。
-4. **`code_url` 不外发**。任何面向商户或前端的应答里不出现它。
+4. ~~**`code_url` 不外发**。任何面向商户或前端的应答里不出现它。~~ **2026-09-22 作废**:用户裁定老开放接口不改(§4.7),该接口照常回 `codeUrl`。**新链路(收银台 / 收款码)自己的输出里仍然不带它**,见 §4.4 的字段名口径。
 5. **文案零上游痕迹**:页面、错误消息、应答字段名里不出现上游名称、域名、其专有字段名。
 6. **凭据不进仓库**。进件新增的 `channel_id` 等一律走配置 + 环境变量,照 `application.yml` 现有的 `${ENV:default}` 写法。顺带记录一条**既存观察**(本轮不改):`leshouying.key` 的默认值目前是明文写在被 git 跟踪的 `application.yml` 里,正式密钥上线前应改为无默认值的环境变量。
 7. 秘密只打印长度,不打印值。
@@ -526,7 +541,7 @@ CREATE INDEX        IF NOT EXISTS idx_receive_code_mch ON pay_receive_code (merc
   - **授权回跳**:`/auth` 出参是带签名的授权地址;回跳带 `openid` 时页面把它存进 `sessionStorage` 并用 `history.replaceState` 抹掉 URL 上的值。
   - **不泄露上游**:`/pay` 应答里**不含**上游订单号、上游域名、上游字段名。用**字段名白名单**断言,不要用「没看到就算过」。
   - 免鉴权:`/payment/cashier/**` 无 token 可达;`/payment/collection/receive/**` 无 token **不可达**。
-  - §4.7 若裁决为「改」:`/qrcode/create` 应答**不含** `codeUrl`、**含** `cashierUrl`;裁决为「不改」则**保留**现有断言。**别按未裁决的版本写测试。**
+  - `/qrcode/create` 应答**保持原样**(§4.7 已裁定不改),现有断言**保留不动**。
 - **批次三**:三-A 的六个接口按报文契约单测 + 签名单测;三-B 的字段组装与必填校验单测,**其联网行为不做自动化测试**(会被外部环境卡住),改为拿到凭据后手工跑一次并留证据。
 - `mvn test` 需要库隧道在位(`PaymentApplicationContextTest` 的已知依赖)。
 
@@ -549,11 +564,31 @@ CREATE INDEX        IF NOT EXISTS idx_receive_code_mch ON pay_receive_code (merc
 1. **批次顺序**:一 → 二 → 三 是否认可?三-A 能否单独先做(它不依赖任何外部输入)?
 2. **§6.3 的 1–6 谁来要**:`channel_id` / `bd_id` / `rate_code` / `account_bank_code` / `channel_type` 需要找客户经理;进件审核通知地址需要上游配置。
 3. **§6.2 (a) 我裁定 `1=企业, 2=个体工商户, 3=小微商户`**,是否认可(依据是三处互相印证 vs 一处有复制痕迹)?
-4. **§4.7 开放接口应答 `codeUrl` → `cashierUrl` 是破坏性变更**,确认可以改(前提是乐收赢确实没上过生产)。**这条与 §0.6「原有的开放接口不变」直接冲突,默认按 §0.6 不动,请裁决。**
+4. ~~**§4.7 开放接口应答 `codeUrl` → `cashierUrl` 是破坏性变更**~~ → **已裁定:不改**(2026-09-22,理由逐字见 §4.7)。与 §0.6 的冲突就此消解。
 
 ### 11.1 §0.9 新需求带来的待确认(2026-09-22 新增)
 
-5. **§4.2 渠道不符时软处理**:微信码被支付宝扫到,我默认**不拦、按 UA 放行**,只在页面上给一句提示。若要改成硬拦,请说一声(改动只在收银台页一处)。
-6. **金额输入范围**:我默认「最低 0.01 元、上限做成配置项」,未定上限值——**商户单笔能收多少,你给个数**(上游 `total_amount` 是 `string(8)`,理论上限 999,999.99 元)。
-7. **收银台对外域名**:§5.3 的 `leshouying.cashier-base-url` 要指向哪个域名?现有 `notify-url` 用的是 `https://qcsj88888.com/api/v1/...`,收银台是否用同一个域名。
-8. **上游侧的两件事要不要现在就去问**(不阻塞本地开发,但阻塞上线):① 正式环境的 `redirect_uri` 是否需要预先登记白名单;② 支付宝回跳时携带用户标识的字段名(文档只写了微信侧示例)。
+**已裁定:**
+
+5. ~~渠道不符怎么办~~ → **不用管**(用户逐字:「微信扫不了支付宝的码,反过来也是一样,不用管」)。落点见 §4.2:不做硬拦、不做提示,`channel` 不参与服务端判断。**已落地文档。**
+7. ~~收银台对外域名~~ → **与 `notify-url` 同源**(用户答「可以」)。落点见 §5.3。**已落地文档。**
+
+**仍待用户给数:**
+
+6. **金额上限**:下限已定 `0.01` 元。上限还差一个数——上游 `total_amount` 是 `string(8)`,理论上限 999,999.99 元。**这个值在仓库里没有任何依据可推,我不编**,等你给。
+
+**仍待决定(不阻塞本地开发,阻塞上线):**
+
+8. **上游侧要不要现在就去问**:正式环境的 `redirect_uri` 是否需要预先登记白名单。(原先并列的「支付宝回跳字段名」**不是未知项**——`getauth2` 那页明写两个渠道都回 `openid`,见 §2.4 订正。)
+
+### 11.2 联调时必须实测的项(不是待确认,是待取证)
+
+下列各项**目前都只是推断或半证据**,本文档里凡引用它们的地方都标了性质。联调时逐条取得真实读数后再回填:
+
+| # | 待验项 | 现状 | 怎么验 |
+|---|---|---|---|
+| 1 | **真机走通完整一单**(三通道各一次) | 未做。本文档所有「能付成」都是从实测推出的**推断** | 微信内扫码 → 输金额 → 唤起 → 支付成功 → 回调入账 |
+| 2 | 支付宝侧的**回跳与唤起**(不是字段名——字段名文档已明写,见 §2.4 订正) | 微信侧实测可达;支付宝侧只验到「UA 会被静默授权」这一步 | 同上,走一单真支付宝:看回跳带没带 `openid`、`jspay` 回没回 `ali_trade_no` |
+| 3 | **上游回跳时 `?openid=` 是怎么拼上去的** | 文档示例里 redirect_uri 带 query 时拼 `&openid=`,还会插一个 `/`;**我们的 redirect_uri 不带 query**,那到底拼 `?` 还是 `&`、会不会插 `/`——**没实测过**(探针没走完整趟 OAuth) | 真机看回跳 URL 原文;若拼错,`/auth` 侧要能容错解析 |
+| 4 | 普通浏览器(手机相机扫)走 `v3/h5pay` 的兜底 | `h5pay` 本身实测过(对照探针 `PAY_SUCCESS`),但**没串进收银台链路** | 用相机扫码走一遍 |
+| 5 | 上游 `redirect_uri` 正式环境是否要预登记白名单 | 未知(测试环境实测未校验) | 问上游(§11.1 第 8 条) |