# Tasks: LINE Pay 直连支付 **Input**: `spec.md`, `plan.md`, `research.md`, `data-model.md`, `contracts/api.md` **Tests**: 本功能涉及支付、凭证、外部 HTTP 和并发状态,所有实现任务必须先添加失败测试。 ## Phase 1: Planning and safety gates - [x] T001 将 `.specify/feature.json` 指向 `specs/019-line-pay`,并把 `spec.md` 标记为 2026-08-12 已批准 - [x] T002 完成 `research.md`、`data-model.md`、`contracts/api.md`、`quickstart.md` 和 `plan.md` - [x] T003 阅读并执行支付安全、TDD 和执行计划技能;运行 spec-kit prerequisite 检查并记录初始 dirty worktree,确保不覆盖用户的 `updatesql/sql.md` 和 observation 变更 ## Phase 2: Persistence foundation (ruoyi-system) - [x] T004 [P] 在 `ruoyi-system/src/test/java/com/ruoyi/system/service/impl/PosStoreLinePayServiceImplTest.java` 写失败测试:相同凭证幂等、轮换新版本、并发只一个 current、失败验证不切换 - [x] T005 [P] 在 `ruoyi-system/src/test/java/com/ruoyi/system/service/impl/PosOrderLinePaymentServiceImplTest.java` 写失败测试:创建 REQUESTING、同订单唯一活跃、稳定键查询、CAS 状态、只有明确终态释放、App 选择多 PAID 转人工核对 - [x] T006 [P] 在 `ruoyi-system/src/test/java/com/ruoyi/system/service/impl/PosOrderLineRefundServiceImplTest.java` 写失败测试:paymentId 唯一、claim/UNKNOWN/REFUNDED CAS 和扫描租约 - [x] T007 在 `ruoyi-system/src/main/java/com/ruoyi/system/domain/` 新建 4 个实体,在 `domain/dto/` 新建 `StoreLinePayCredentialDto.java`、`StoreLinePayToggleDto.java`,在 `domain/vo/` 新建 `PosStoreLinePayVo.java` - [x] T008 在 `ruoyi-system/src/main/java/com/ruoyi/system/mapper/` 与 `ruoyi-system/src/main/resources/mapper/chanting/` 新建凭证、支付、退款、网关日志 Mapper/XML;所有状态推进使用 expected status + version CAS,扫描使用覆盖索引条件 - [x] T009 在 `ruoyi-system/src/main/java/com/ruoyi/system/service/` 和 `service/impl/` 实现 4 个持久化服务,使 T004-T006 通过 - [x] T010 在 `ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosOrderMapper.java` 与 `resources/mapper/system/PosOrderMapper.xml` 添加订单支付渠道领取、支付事实、退款事实的条件更新,并补相应 Mapper/Service 测试 - [x] T011 运行 `mvn -pl ruoyi-system -am -Dtest='*Line*Test' -Dsurefire.failIfNoSpecifiedTests=false test` ## Phase 3: LINE v4 client and credential management (ruoyi-admin) - [x] T012 [P] 在 `ruoyi-admin/src/test/java/com/ruoyi/app/utils/linepay/LinePaySignerTest.java` 写官方格式的 POST JSON、GET 精确 query、非 ASCII、空 body 和 nonce 签名失败测试 - [x] T013 [P] 在 `ruoyi-admin/src/test/java/com/ruoyi/app/utils/linepay/LinePayClientTest.java` 写 method/URI/body/header、字符串 transactionId、超时分档、Request packages、默认浏览器 redirect、full refund `{}` 契约测试 - [x] T014 在 `ruoyi-admin/src/main/java/com/ruoyi/app/utils/linepay/` 实现 properties、signer、transport 和 client;固定 base URL 配置,不允许请求控制目标地址 - [x] T015 在 `ruoyi-admin/src/test/java/com/ruoyi/app/mendian/PosStoreLinePayControllerTest.java` 写凭证探测 `1150/0000/认证错误/网络未知`、详情权限、普通列表不含 Secret 的失败测试 - [x] T016 在 `ruoyi-admin/src/main/java/com/ruoyi/app/mendian/PosStoreLinePayController.java` 实现列表/详情/save/toggle,使用明确 DTO 和业务 i18n;在 `application.yml` 添加 LINE 非秘密配置并移除有效 Zalo sandbox 配置块 - [x] T017 在 `ruoyi-admin/src/main/java/com/ruoyi/app/pay/LinePayGatewayAuditService.java` 实现追加日志包装,测试日志失败不回滚/改变支付事实 ## Phase 4: Create, query and redirect page - [x] T018 [P] 在 `ruoyi-admin/src/test/java/com/ruoyi/app/pay/LinePayServiceCreateTest.java` 写失败测试:用户归属、payType=3、历史 Zalo 无 Line 行不识别、单门店、金额、凭证、重复点击复用、明确终态新增行、Request UNKNOWN - [x] T019 [P] 在 `ruoyi-admin/src/test/java/com/ruoyi/app/pay/LinePaySelectionTest.java` 写 App 查询选择规则与旧回跳不污染新尝试测试 - [x] T020 [P] 在 `ruoyi-admin/src/test/java/com/ruoyi/app/pay/LinePayReturnPageRendererTest.java` 写 HTML/JS/URL 转义、固定 Scheme、安全响应头、无同步 Confirm 测试 - [x] T021 在 `ruoyi-admin/src/main/java/com/ruoyi/app/pay/PaymentCreateGuardService.java` 实现共享 watchdog 锁 + 订单条件领取,并对 `OmgPayController#create` 做仅限 create 门禁的最小调整及回归测试 - [x] T022 在 `ruoyi-admin/src/main/java/com/ruoyi/app/pay/LinePayService.java` 实现 create/query 与独立事务 intent/result;在 `pay/dto/` 新建请求/响应 DTO - [x] T023 在 `ruoyi-admin/src/main/java/com/ruoyi/app/pay/LinePayController.java` 实现 create/query/confirm/cancel;confirm/cancel 快速返回 self-contained HTML,cancel 不写终态 - [x] T024 修改 `ruoyi-system/src/main/java/com/ruoyi/system/domain/PosOrder.java` 和 `ruoyi-admin/src/main/java/com/ruoyi/app/order/dto/OrderPositionInfo.java` 的有效 payType 注释为 `3=LINE Pay`,不修改废弃 Zalo 实体/Controller ## Phase 5: Reconcile, payment facts and refund - [x] T025 [P] 在 `ruoyi-admin/src/test/java/com/ruoyi/app/pay/LinePayReconcileTest.java` 覆盖 Retrieve 唯一 CAPTURE、1150→Check、0000/0110/0121/0122/0123、Confirm UNKNOWN、deadline→MANUAL_REVIEW - [x] T026 [P] 在 `ruoyi-admin/src/test/java/com/ruoyi/app/pay/LinePayCancellationRaceTest.java` 覆盖支付先成功后取消、取消先发生后迟到成功、堂食 state=1、退款 intent 唯一 - [x] T027 [P] 在 `ruoyi-admin/src/test/java/com/ruoyi/app/pay/LinePayRefundTest.java` 覆盖全额退款省略 amount、UNKNOWN 只 Retrieve、refundList 恢复、PAID 不改写、订单 payStatus=2 - [x] T028 在 `LinePayService` 实现 Retrieve-first 状态机、Confirm 本地门禁、支付事实事务、退款状态机与凭证版本解析,使 T025-T027 通过 - [x] T029 在 `ruoyi-admin/src/test/java/com/ruoyi/app/task/LinePayReconcileTaskTest.java` 写 watchdog 锁、batch=20、时间预算、行租约、单笔失败继续和取消订单不排除测试 - [x] T030 在 `ruoyi-admin/src/main/java/com/ruoyi/app/task/LinePayReconcileTask.java` 实现每分钟支付/退款恢复任务,不调用 Controller,不修改 `TestTask.java` - [x] T031 修改 `UserOrderController` 和 `PosOrderShOprateController` 的真实取消入口:取消后调用 create-refund-if-absent;商家入口先按 userType/shId/mdId 鉴权 - [x] T032 修改 `PosOrderShOprateController` 接单入口并禁止真实 LINE 订单走 `PosOrderController#/setorderuzt`;历史 payType=3 且无 Line payment 不进入新 LINE 资金动作 - [x] T033 扩展 `OrderLifecycleService`、`AdminOrderStatusContext` 和 `PosOrderController`:显示 LINE 上下文,提供独立权限的人工 reconcile/full refund,并补现有 OrderLifecycle 测试 - [x] T033A 管理全额退款先在订单行锁内创建唯一退款意图;用户、商家、骑手真实 LINE 完成入口统一使用拒绝非终态退款意图的 CAS,并补并发回归测试 - [x] T033B 退款 PROCESSING 崩溃恢复时初始化 UNKNOWN 截止时间,UNKNOWN 无结论查询使用版本 CAS 退避重排,避免批次队首饥饿 - [x] T033C 定时恢复为支付、取消补偿和退款保留独立执行机会,支付领取租约时同步重排;真实 LINE 用户完成统一触发结算副作用;骑手接单、取餐、送达统一校验骑手角色、合法状态和订单归属 - [x] T033D 退款 UNKNOWN 到达截止时间后执行最后一次只读 Retrieve,不再先重排版本;人工核对 CAS 检查影响行数并重读真实状态,避免接口状态与数据库不一致 - [x] T033E 旧凭证明确定鉴权失败时,仅以同门店/环境/Channel 当前版本执行严格 Retrieve 证明后恢复;不同 Channel 或证据不符禁止 Confirm/Refund;REFUNDED 在应用层和 SQL 层永久禁止降级为人工核对 - [x] T033F 为严格 Retrieve 已证实的部分/歧义退款提供专用证据 CAS,允许 FAILED 升级人工核对但永久排除 REFUNDED,避免资金证据被通用状态白名单吞掉 - [x] T033G 为严格 Retrieve 已证实的全额退款提供专用证据 CAS,允许 FAILED 升级已退款但排除 MANUAL_REVIEW/REFUNDED,避免外部全退事实被历史本地失败状态吞掉 ## Phase 6: SQL, messages and platform Vue - [x] T034 在 `updatesql/sql.md` 末尾追加 2026-08-12 LINE DDL、索引、页面公共 `chanting:storePayment:list` 和 LINE list/query/save/toggle、订单 reconcile/refund 权限 SQL;不改写既有 OMG SQL - [x] T035 在 `ruoyi-admin/src/main/resources/i18n/messages.properties` 及 `messages_{zh_CN,zh_TW,en_US,vi}.properties` 添加相同 LINE 业务错误 key - [x] T036 在 `foodie-admin-vue/src/api/chanting/storeLinePay.js` 新建凭证 API;在 `views/mendian/storePayment/components/LinePayTab.vue` 实现列表、详情、凭证验证/轮换和启停;修改 `index.vue` 替换占位页并保持 CRLF - [x] T037 在平台四个 `src/api/language/language.*.js` 文件添加完全相同的 `storeLinePay` key,并调整 `storePayment` 文本,不硬编码用户可见中文 - [x] T038 修改平台 `src/api/system/order.js` 与 `src/views/system/order/index.vue`,显示 LINE 状态并按独立权限提供查询/全额退款;UNKNOWN/MANUAL_REVIEW 明确提示不可重复副作用 ## Phase 7: Full verification and delivery - [x] T039 运行所有 LINE 定向测试和 `OrderLifecycleServiceTest,PosOrderAdminStatusControllerTest` 回归(99 项通过);OMG 全量测试仍有与本功能无关的既有日志脱敏期望差异,未修改 OMG - [x] T040 使用 JDK 21 运行 `mvn -pl ruoyi-admin -am -DskipTests package` - [x] T041 在 `foodie-admin-vue` 对 LINE 新增文件运行 lint、运行 production build、核对四语 `storeLinePay` 32 个子 key 集合和 Vue CRLF - [x] T042 运行 `git diff --check`、检查 `git status --short` 和 diff 统计;确认无数据库执行、无废弃代码改动、无用户文件覆盖 - [x] T043 对照 `quickstart.md` 完成可在本地模拟的验收;真实 Sandbox、LINE App 和商户 App Scheme 真机验证留在交付说明 ## Dependencies - T004-T006 可并行写测试;T007-T010 使其通过后才能开始业务编排。 - T012-T013 可并行;T014 完成后执行 T015-T017。 - T018-T020 可并行;T021-T024 完成 create/query/redirect。 - T025-T027 可并行;T028 完成后再接任务和订单入口 T029-T033。 - 后端契约稳定后执行 T034-T038;最后统一 T039-T043。 ## Definition of Done - 同一订单可保留多条历史 LINE 尝试,但只有一条阻断性活跃行。 - 凭证轮换不覆盖历史版本,每笔支付持有 credentialId。 - 所有支付/确认/退款未知结果都可只读恢复,绝不盲重试副作用。 - 取消竞态最终只一次全额退款;payment 保持 PAID,退款成功后 order.payStatus=2。 - App 查询、回跳、任务、平台动作都通过稳定键定位正确尝试。 - 平台四语、权限、SQL 和最小 OMG create 门禁完成;目标测试、编译与前端检查通过。