Files
cmbuyer/docs/tasks/T-402.md
T

102 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: T-402
title: 待付款人工收口与付款事实记录
phase: 4
deps: [T-403]
status: TODO
created: 2026-08-04
vikunja_task_id: 51
context_ref: 8600c33
work_branch: task/t-402-payment-closure
needs_device: false
needs_human_review: true
write_paths:
- docs/tasks/T-402.md
- admin/migrations/00009_payment_closure.sql
- admin/internal/migrations/migrations_test.go
- admin/internal/domain/payment_closure.go
- admin/internal/domain/payment_closure_test.go
- admin/internal/paymentclosure/**
- admin/internal/taskdetail/**
- admin/internal/server/mark_paid.go
- admin/internal/server/mark_paid_test.go
- admin/internal/server/router.go
- admin/internal/server/task_detail.go
- admin/internal/server/task_detail_test.go
- admin/internal/transport/webui/webui.go
- admin/internal/transport/webui/templates/task-detail.html
- admin/cmd/server/**
- admin/README.md
- docs/api.md
- docs/04-architecture.md
- docs/routes.md
- docs/08-interaction-checklist.md
- docs/current-state.md
---
<!-- BEGIN VIKUNJA EXPORT id=51 synced=2026-08-04T15:03:09Z sha256=c9d2806cefd7ce0cbbb96bd1b764eb54ff713cfb25715daa3581933c2f74ec4c -->
## 背景
T-208 已冻结创建待付款订单后的 `WAITING_PAYMENT` 事实:仅当提交记录为 `SUBMITTED`,或管理员调和结论为 `CONFIRMED_CREATED` 时,系统才知道订单已创建。T-403 负责展示这些既有事实。本任务只记录管理员已经在拼多多人工完成真实付款这一外部事实,并把任务收口为成功;系统本身没有付款,也不得验证或自动执行付款。
## 关联
- Feature:F011
- Story:US006
- Integration:IX006
- 前置任务:T-403
- 后续消费者:T-404
## 实现方案
1. 新增唯一迁移 `00009_payment_closure.sql`,建立追加式 `payment_closures`:绑定同一 lineage 的 task、submission、attempt、authorization,以及管理员 actor、服务端 UTC 记录时间、幂等键与规范化请求摘要。唯一约束保证同一任务/提交只能收口一次,并保证同一管理员幂等键不可复用为不同请求;外键/约束必须阻止跨 lineage 事实。不得保存付款方式、账户、卡号、订单凭据、地址、手机号或截图/XML。migration down 只允许在 `payment_closures` 为空时执行;存在任意事实必须在删除表或约束前原子拒绝,禁止丢失付款审计事实。
2. 在任务详情的 `WAITING_PAYMENT` 区域明确显示“订单已创建,系统尚未付款”,同时展示商品、规格、数量、金额上限,以及 T-208/T-403 已有提交、调和和三道闸门证据。不得暗示系统已付款。
3. 管理员必须先在拼多多外部人工完成真实付款,再完成三个且仅三个确认组:`goods_spec_quantity_confirmed` 表示商品/规格/数量已核对,`amount_within_cap_confirmed` 表示订单金额未超过授权上限,`externally_paid_confirmed` 表示管理员已在拼多多人工完成真实付款。三项 wire 值必须全部严格为 `true`;任一缺失、为 `false` 或无法严格解析均返回 400 且零写入。按钮文案明确为“记录人工付款并标记完成(不执行付款)”。
4. `POST /tasks/{id}/mark-paid` 仅接受管理员会话与 CSRF,通过严格表单读取 UUIDv4 幂等键、expected task version、当前 submission id 和上述三个固定布尔确认组,不接受别名或额外确认字段。actor 只能来自会话,时间只能来自服务端 UTC。
5. 在单一事务和写锁内重新读取 task、submission、attempt、authorization:四者必须属于同一 lineage;任务必须正好是 `WAITING_PAYMENT`,submission 必须正好是该任务当前唯一提交且关联同一 attempt/authorization,authorization 必须正好是 `CONSUMED`。submission 必须为 `SUBMITTED`,或为已有 T-208 `CONFIRMED_CREATED` 最终调和事实的 `MANUAL_RESOLVED`。旧 task version、错误/过期 submission 固定返回 409 且零写入;任一 lineage、authorization、submission 或 task 状态冲突也必须返回 409、零写入,并让详情进入只读安全核查,绝不能推进为 `SUCCEEDED`。
6. 所有检查通过后,同一事务追加 payment closure,并将任务从 `WAITING_PAYMENT` 单向推进到 `SUCCEEDED`、version + 1。submission、attempt、authorization 和已消费的提交围栏永久不可变,不得重开、释放、重置、重新授权或重新提交。
7. 幂等性必须先查事实:同 key、同规范化 payload 在响应丢失和进程重启后稳定重放同一结果;同 key、不同 payload 返回 409;不同 key 在任务已收口后返回 `payment_already_recorded` 409。规范化 payload 必须包含 task、submission、expected version 和三个固定确认组。不得仅因任务已是 `SUCCEEDED` 就伪造成功响应。
8. 成功页/详情页显示记录 actor、服务端时间和“仅记录人工付款事实,系统未执行付款”。失败保留用户输入并给出可操作提示,焦点回到错误摘要;事实冲突时只显示安全核查,不提供重试提交、释放或重新授权动作。
9. 自动化测试只使用本地数据库夹具构造 `WAITING_PAYMENT` 和既有提交事实,覆盖事务、冲突、幂等、重启与 UI;不得连接拼多多、请求真实资金、模拟付款控件或要求测试人员实际付款。`needs_human_review` 只验收语义、权限和交互,不代表测试中发生真实付款。
## 验收
- 三个且仅三个 wire 确认组固定为 `goods_spec_quantity_confirmed`、`amount_within_cap_confirmed`、`externally_paid_confirmed`;三项全部严格为 `true` 才能继续,逐项覆盖缺失、`false`、非法值和额外别名,均返回 400 且零写入。
- 只有管理员、有效 CSRF、精确 task version、精确当前 submission、同 lineage 的 task/submission/attempt/authorization 和 `CONSUMED` authorization 能写入;旧版本和错误/过期 submission 稳定返回 409 且零写入。
- 覆盖 `SUBMITTED` 与 `CONFIRMED_CREATED` 两条允许路径,以及跨 task/attempt/authorization、authorization 非 `CONSUMED`、其它任务/提交状态和事实冲突;冲突均返回 409、零写入、进入只读安全核查且绝不 `SUCCEEDED`。
- 覆盖并发双击、同键同载荷重放、同键异载荷冲突、不同键重复收口和进程重启;事实表和任务状态在同一事务提交或回滚。
- migration 覆盖 up、重开、外键/唯一/lineage/append-only 约束、空表 down 成功、存在任意 payment closure 时 down 在删表前原子拒绝,以及 `foreign_key_check`。
- 页面显著显示“系统尚未付款”和“不执行付款”,键盘操作、焦点、错误摘要、安全核查态及移动宽度可用。
- 自动测试不需要真实付款,不访问手机或拼多多;代码不得包含任何支付、免密、先用后付或扣款控件操作。
- 运行 `go test ./...`、`go test -race ./...`、`go vet ./...`、`go build ./...`、前端交互检查、根目录初始化门禁、上下文校验、导出校验和 `git diff --check`。
## 执行记录
(暂无)
<!-- END VIKUNJA EXPORT -->
## 边界
- `mark-paid` 只记录管理员已经在拼多多外部人工完成真实付款的事实;系统没有执行付款,也不得发起、
验证、模拟或自动化支付,不得读取或保存付款方式、账户、卡号、订单凭据、地址或手机号。
- 三个且仅三个确认组固定为 `goods_spec_quantity_confirmed`、`amount_within_cap_confirmed`、
`externally_paid_confirmed`;三项 wire 值必须全部严格为 `true`。任一缺失、为 `false`、非法值或
别名返回 400 且零写入;旧 task version 与错误/过期 submission 固定返回 409。
- 只允许精确处于 `WAITING_PAYMENT` 的任务和精确当前 submission 收口。事务必须确认 task、
submission、attempt、authorization 属于同一 lineage,authorization 正好为 `CONSUMED`;
submission 必须是 `SUBMITTED`,或已有 T-208 `CONFIRMED_CREATED` 最终调和事实。任一归属、
状态或事实冲突都返回 409、零写入并进入只读安全核查,绝不能推进为 `SUCCEEDED`。
- actor 只能取自已认证管理员会话,记录时间只能取服务端 UTC。幂等事实必须在任何状态短路前检查:
同 key 同规范化载荷稳定重放,同 key 异载荷返回 409;不同 key 重复收口返回
`payment_already_recorded` 409,不得仅因任务已是 `SUCCEEDED` 就伪造成功响应。
- payment closure 事实与 task `WAITING_PAYMENT → SUCCEEDED`、version + 1 必须在同一事务内原子
提交或回滚。submission、authorization 和已消费围栏永久不可变,不得重开、释放、重置、重新授权、
重新领取或重新提交。
- 本任务唯一新增 migration 是 `00009_payment_closure.sql`;事实表只保存同一 lineage 的 task、
submission、attempt、authorization、actor、服务端时间、幂等键和请求摘要,不保存截图、XML、
支付凭据或新的 evidence kind。`payment_closures` 为空时才允许 migration down;存在任意事实时
必须在删表或约束前原子拒绝,禁止丢失付款审计事实。
- 自动化测试只使用本地数据库夹具,不访问手机或拼多多,不请求真实资金,也不要求测试人员真实付款。
`needs_human_review` 只验收管理 UI 的未付款语义、权限、确认和无障碍交互。
- 本任务不修改 `client/` 或拼多多页面判据,不点击支付、免密支付、先用后付或任何扣款控件。