Files

102 lines
9.4 KiB
Markdown
Raw Permalink Normal View History

2026-08-04 23:20:28 +08:00
---
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/` 或拼多多页面判据,不点击支付、免密支付、先用后付或任何扣款控件。