# 乐收赢「收款链接 + 自助收银台」交付说明(2026-09-30) 计划:`.claude/plan/2026-09-30-lsy-checkout-link.md`(同批入库) 需求原文(用户 2026-09-30,两句话定的范围): ①「lsy渠道的主扫是一码一收,做不了静态码,我们自己要做一个checkout,主扫api提供的将是一个https链接,  这个链接就与该商户绑定,链接可以无限次使用,这个链接打开的就是我们的checkout,用户自己在checkout  输入金额然后拉起实际支付」 ②「checkout链接获取的按钮可以做到系统页面上,顺便封装成二维码,链接和二维码都可以拿走直接用」 ## 一、提交清单(5 个,均**未 push**) | 提交 | 内容 | |---|---| | `87536f0c` | 后端:建表 SQL(只写不执行)、`LsyCheckoutLinkService`、免登录两个端点、管理端与开放 API 两条发链接的腿 | | `6c663b97` | 前端顾客页 `/#/checkout/:token` + 免登录判据(`permission.ts`)+ 静态路由 | | `e6035c06` | 商户页操作列「收款链接」按钮 + 二维码 + 复制 / 下载 PNG | | `3c47de8c` | 接入文档 §5.8 + 侧边栏条目(含 §1 接口计数 10→11、§3.5 信封清单两处顺带订正) | | `1e925a21` | 实现计划(含**计划与落地的两处偏差**:axios 实例没单独成文件、弹窗没单独成组件) | ## 二、验证读数(都是本机实测,不是推断) - `mvn test` 全量 **1007 条,0 失败 0 错误 0 跳过**(14:08) - 前端执行级验证台(真 SFC 编译 + 真 vue + 驱动真函数;本仓无测试框架、`ts:check` 有历史非零基线): **65 条断言全绿** - 文档切片:**8 条断言全绿** —— 11 条侧边栏定义 == 11 条产出(一条都没静默消失) - `vue-tsc` 总量 **289 条**(与 09-28 基线同数);**本次碰过的 6 个前端文件在其中 0 命中** - 构建:`java/target/payment-platform-1.0.0.jar` 重打于 **14:08:42**(sha256 `1d0fca8d5b5f2815…`, 内含 9 个 checkout class 与 `db/lsy-checkout-link.sql`);`frontend/dist` 已重建 (§5.8 在 `js/index.CGO9EMvq.js`,顾客页在 `js/index.D8IdXt0n.js`) 两向变异(每条都先验「变异体确实生效」,还原后逐文件 sha256 复核回基线): | 变异体 | 结果 | |---|---| | M1 元→分改回浮点乘法 | 红 6(**拒绝类**向量与安全整数守卫;≤2 位小数的合法输入两种写法同值 —— 不能说成「浮点会算错钱」) | | M2 下限改 1 分 | 红 3 | | M3 免登录判定挪进已登录分支 | 红 1(未登录那条) | | M3b 免登录判定挪到 try 末尾 | 红 3,读数 `next` 被调**两次**:先 `/login?redirect=%2Fcheckout%2F%3Atoken` | | M4 无害改名(两个文件) | **仍绿 65/65** | | M5 二维码只编码 token 而非整条 url | 红 1 | | M6 路由 `name` 改名 | 红 6 | ## 三、部署要求(按顺序,第 1 步不做就没有后面) 1. **先执行建表 SQL**:`java/src/main/resources/db/lsy-checkout-link.sql`。 本工程未接 Flyway,脚本由人工执行;**只能由你执行**。 ★ 这张表**原先不存在** ⇒ 不建表就部署,管理端按钮、开放 API 那条腿、顾客页开屏 三处**全部** 500/404。 2. **重新部署**:`java/target/payment-platform-1.0.0.jar` + `frontend/dist`(两者都已就绪)。 3. **重新登录**(权限串复用商户 `:list`,**没有菜单 SQL 要跑**,菜单也不变)。 4. 验收路径:乐收赢商户页 → 操作列「收款链接」→ 弹窗里「复制链接」/「下载二维码」 → 手机打开那条链接 → 输金额(≥1 元)→ 确认 → 应跳到支付宝收银台。 **不需要改 `application.yml`。** `leshouying.checkout-base-url` 有代码默认值(留空 ⇒ 按请求头推导:`X-Forwarded-Proto` / `X-Forwarded-Host` / `Host`)。 ★ 但**基址取自调用方访问的域名**:如果后台是用 `IP:端口` 打开的,取到的链接就是那个地址、 顾客打不开 —— 那种情况下要么把 `checkout-base-url` 配成公网域名,要么用公网域名访问后台。 同一个商户,从管理端取到的链接与从开放 API 取到的链接,基址可能**不同**(同因)。 ## 四、未验证 / 未做(逐条,都是交付时的实测状态) 1. **这条路一笔真钱都没收过。** 建表 SQL 未执行、未部署 ⇒ 顾客页开屏、下单、跳转 三个环节都**没有线上读数**。上面全部读数来自单测与执行级验证台。 2. **真机浏览器未验。** 顾客页是手机优先设计,但只在验证台里驱动过函数,没有在手机上打开过。 3. **二维码的实际可读性未验。** 下载机制(文件名、href、click、位图尺寸)验了, 把 PNG 印出来**用手机扫**没验。 4. **链接没有撤销入口。** 表里有 `status` 列,服务层也认它(`DISABLED` ⇒ 按不存在处理, 同一条 404),但**没有任何接口能把它改成 DISABLED**。一条链接流出去了,目前**只能改库**。 本批有意不做(用户只要求「能拿走直接用」),验收时请知情。 5. **同一商户换一条新链接也不行。** `uk_lsy_checkout_tenant_mch` 让一商户恒等于一条链接, `issue` 又对同一条幂等 ⇒ 想让旧链接作废**没有接口**(同第 4 条,同一个根因)。 6. **商户改名不会自动刷新链接行上的名字快照。** 快照在「重新取一次链接」时才刷新 (顾客页显示的是快照)。已写进文档与类注释。 7. **开放 API 那条腿的 `merchant_code` 留空 + 租户登记了多个商户号 ⇒ 400**(不替调用方挑), 与三个下单端点同一条分流;这条**没有**联调过真请求。 8. **计划与落地有两处偏差**(axios 实例没单独成文件、弹窗没单独成组件),已写进 `1e925a21` 的提交消息,计划文件本身**没有改**。