tasks.md 13 KB


description: "Task list for 031-pay-method-config"

Tasks: 可配置支付方式(031-pay-method-config)

Input: Design documents from /specs/031-pay-method-config/

Prerequisites: plan.md ✅ spec.md ✅ research.md ✅ data-model.md ✅ contracts/api.md ✅ quickstart.md ✅

Tests: 按项目惯例含单测任务(JUnit5+Mockito,参照 InfoUserControllerTest 手动 mock 模式)

Organization: 按 spec 六个用户故事分组;分支直接在 test-202609v2,不建 feature 分支

全局规范(每个任务都适用,不再逐条重复):

  • 新代码全注释:类 Javadoc + 方法 Javadoc(做什么+为什么)+ 实体/DTO 每字段注释
  • Controller:@RequestHeader token / @RequestBody DTO / @RequestParam,DTO 无 Bean Validation,错误走 MessageUtils.message
  • 后端 i18n key 进 messages*.properties 全部 6 文件;前端 key 进 4 语言文件(有意义驼峰命名)
  • 数据库变更只写 updatesql/sql.md 不执行;前端文件 CRLF 用 Python 脚本编辑;Java 注释禁 */

Phase 1: Setup (Shared Infrastructure)

Purpose: 一次性落全部数据库变更文档

  • T001 将 031 全部 DDL 与迁移 SQL 追加到 updatesql/sql.md(2026-09-11 标注):新表 payment_method_config(uk(method_code,scope),缺行=开)、新表 info_bank_cardinfo_userpay_methods/flash_pay_methods 两列、flash_delivery_orderpayment_status、027 三字段迁移 INSERT...SELECT(结构见 data-model.md 第 1-5 节,逐条含用途注释)

Phase 2: Foundational (Blocking Prerequisites)

Purpose: 闸门服务与共享实体——所有故事的前置

⚠️ CRITICAL: 本阶段完成前不得开始任何用户故事

  • T002 [P] 新建实体 PaymentMethodConfig + PaymentMethodConfigMapperruoyi-system/src/main/java/com/ruoyi/system/domain/mapper/,MyBatis-Plus,全字段注释,字段见 data-model.md 第 1 节)
  • T003 [P] 新建实体 InfoBankCard + InfoBankCardMapper(同上目录,字段见 data-model.md 第 3 节;user_id 索引在 T001 DDL 已含)
  • T004 [P] InfoUserpayMethods/flashPayMethods 字段(ruoyi-system/src/main/java/com/ruoyi/system/domain/InfoUser.java,注释注明 NULL=全部;同步其 mapper XML 的 resultMap/列清单若存在)
  • T005 实现 PaymentMethodGateServiceruoyi-system/src/main/java/com/ruoyi/system/service/PaymentMethodGateService.java,依赖 T002-T004):assertUsable(payType, scope, payeeUserId) 三层闸门(平台开关缺行=开 → 收款方选择 NULL=全部 → 就绪度:CARD_OMG 查 pos_store_omg、LINE_PAY 查 pos_store_line_pay、OFFLINE_TRANSFER 查 info_bank_card is_active=1、COD 恒满足;现金 4 不校验直接放行);listAvailable(scope, payeeUserId) 供可选项;开关读取加缓存(DictUtils 同款模式,保存后失效);payType↔组代码映射(2+5→CARD_OMG 联动);i18n key pay.method.not.available 写入 6 个 ruoyi-admin/src/main/resources/i18n/messages*.properties
  • T006 闸门服务单测(ruoyi-system/src/test/java/com/ruoyi/system/service/PaymentMethodGateServiceTest.java):三种拒绝原因各自抛错且消息为国际化 key 文案、缺行=开、NULL 选择=全部、CARD_OMG 组 2 与 5 同开同关、COD 恒过、现金放行、缓存失效后新值生效

Checkpoint: 闸门可用,各故事可并行开始


Phase 3: User Story 1 - 平台支付方式开关 (Priority: P1) 🎯 MVP

Goal: 平台管理员可按方式×维度开关,即时生效,默认全开=现状

Independent Test: 关闭某方式×维度 → 对应下单入口该方式立即不可用(SC-001),表为空时行为与现状一致

  • T007 [US1] 新建 PayMethodConfigControllerruoyi-admin/src/main/java/com/ruoyi/app/pay/):GET /system/payMethodConfig/list 返回全矩阵(缺行按开补齐)、PUT /system/payMethodConfig 批量 upsert;@PreAuthorize("@ss.hasPermi('pay:method:config')") + @Log(title="支付方式设置");保存 DTO 走 Controller 规范;单测 PayMethodConfigControllerTest(矩阵补齐、保存后缓存失效、越权 403)
  • T008 [P] [US1] admin-vue 支付方式设置页:E:\QtwCode\foodie\foodie-admin-vue\src\views\pay\methodConfig\index.vue(方式×维度开关矩阵,参照现有 mendian/storePayment 页面风格)+ 路由 + 菜单按钮权限 SQL(追加 updatesql/sql.md)+ i18n 四语言(admin-vue 语言文件,按命名空间定位插入)
  • T009 [US1] US1 验证:编译 + 全部新单测通过 + 手测开关即时生效(开→关→开,无需重启)

Checkpoint: MVP 达成——平台已可一键下线/恢复任意支付方式


Phase 4: User Story 2 - 商家选择支付方式 (Priority: P1)

Goal: 商家主账号级勾选接受的方式,未设置=全部;用户结算页按可用集渲染

Independent Test: 商家只勾"到付"后用户结算页仅剩到付;清空勾选回落全部(SC-003 存量零变化)

  • T010 [US2] 新建 MerchantPayMethodControllerruoyi-admin/src/main/java/com/ruoyi/app/pay/):GET /system/merchantPayMethods(available+就绪度+selected,NULL 展开为全部)、PUT /system/merchantPayMethods(写主账号行,子账号经 022 MerchantStoreAccessService/主账号体系定位,空数组=清空回落);值域校验组代码合法;单测(子账号写主账号、清空语义、非法组代码拒绝)
  • T011 [P] [US2] 用户端结算页可用方式接口:GET /system/storePayMethods@Anonymous + @RequestParam storeId):pos_store → user_id → listAvailable(MERCHANT, 商家主账号),返回 [{methodCode, payType, ready}] 供 App 结算页渲染(契约已补入 contracts/api.md 第 7 节);单测
  • T012 [P] [US2] foodie-store 商家支付设置页(方式勾选部分):E:\QtwCode\foodie\foodie-store\src\views\PayMethodSettings.vue(参照 SelfDeliverySettings.vue 结构;本任务只做方式勾选区,银行卡区在 T020)+ 路由菜单 + i18n 四语言(src/lang/ zh/tw/en/vi 同 key)

Checkpoint: 餐饮单维度三层闸门完整闭环(平台∩商家∩就绪度 + 结算页渲染)


Phase 5: User Story 3 - 下单校验堵漏洞 (Priority: P1)

Goal: 餐饮两下单入口接统一闸门,绕过界面提交被禁方式 100% 拒绝(SC-002)

Independent Test: curl 直提交被禁 paymentMethod → 国际化错误;老 App 同样被拒

  • T013 [US3] UserOrderController.createOrder 接入闸门(ruoyi-admin/src/main/java/com/ruoyi/app/order/UserOrderController.java:261 setPayType 前调 assertUsable(paymentMethod, MERCHANT, 商家主账号);商家主账号经 pos_store 反查,与 T011 同路径抽取复用);单测(被禁拒绝/放行路径/现金跳过)
  • T014 [US3] PosOrderController /addorder 接入闸门(payType 固定 "1" COD,对称调用 assertUsable,平台关到付时商家建单被拒并提示);单测

Checkpoint: SC-002 达成;闸门调用点=contracts 白名单第 1、2 处


Phase 6: User Story 4 - 骑手闪送收款方式 (Priority: P2)

Goal: 骑手设置接受方式(NULL=全部),闪送下单选方式并记录,抢单列表过滤(SC-005)

Independent Test: 骑手只接受现金 → 转账单不进其列表;未设置骑手全可见

  • T015 [P] [US4] 新建 RiderFlashPayMethodControllerruoyi-admin/.../pay/ 或骑手包):GET/PUT /system/riderFlashPayMethods(token 校验 userType=2,scope=RIDER_FLASH,NULL=全部);单测
  • T016 [US4] 闪送下单/报价接支付方式(ruoyi-admin/.../flashdelivery/):FlashDeliveryQuoteRequest/FlashDeliveryCreateRequest 加可选 payType 字段(注释:缺省现金 4);normalizePayType 从透传改为 assertUsable(payType, RIDER_FLASH, null)(仅平台开关层,抢单前无收款方);home/quote 响应增可选方式列表 listAvailable(RIDER_FLASH, null);相关 DTO 字段注释补齐;单测(非法方式拒绝、缺省现金、可选项返回)
  • T017 [US4] 抢单列表过滤(FlashDeliveryApplicationService.riderOrdersQuery newTask 分支):追加 pay_type ∈ 骑手 flash_pay_methods 展开集合(NULL=平台开放全部集合)条件;FlashDeliveryRiderOrderListViewTest 补过滤用例

Checkpoint: 闪送维度闸门闭环(US3 白名单第 3 处)


Phase 7: User Story 5 - 银行卡列表化与 027 迁移 (Priority: P2)

Goal: 银行卡多卡单启用管理;027 存量迁移零回退(SC-004);bankInfo 结构不变改数据源

Independent Test: 迁移后存量商家转账信息照常展示;启用新卡付款方只见新卡

  • T018 [US5] 新建 BankCardControllerruoyi-admin/.../pay/):CRUD + PUT /system/bankCard/activate/{id}(同事务停旧卡);校验 bankName∈字典 taiwan_bank_list(读取先例 InfoUserController.java:815)、上限 10 张、仅本人卡;i18n key pay.bankcard.*;单测(启用停旧、越权、上限、字典校验)
  • T019 [US5] 027 bankInfo 改造(定位 ChantingStoreController.bankInfo,契约 specs/027-offline-transfer-payment/contracts/api.md):数据源改 info_bank_card is_active=1,返回三字段名结构完全不变;前置套闸门 assertUsable("6", MERCHANT, 商家userId) 不满足返回 data=null;单测(迁移卡透出、无启用卡 null、闸门关闭 null、字段名不变)
  • T020 [P] [US5] foodie-store 支付设置页银行卡管理区(PayMethodSettings.vue 第二块:卡列表/新增/编辑/删除/启用,银行名下拉取 taiwan_bank_list 字典接口)+ i18n 补 key

Checkpoint: US3 白名单第 4 处(bankInfo);027 平滑迁移


Phase 8: User Story 6 - 骑手确认收款 (Priority: P3)

Goal: 闪送订单收款状态记录,不阻塞履约(FR-013)

Independent Test: 未确认不影响送达/完成;确认后 0→1 + 日志;重复确认幂等

  • T021 [US6] 闪送确认收款:FlashDeliveryOrderpaymentStatus(注释:0未收1已确认)+ 同步其 mapper XML;FlashDeliveryRiderControllerPOST /orders/{id}/confirmPayment(本人中单+状态≥已送达;幂等;写 flash_delivery_order_log operator_type=RIDER);用户/骑手订单视图返回 paymentStatus;单测(条件校验、幂等、日志、不阻塞状态流转)

Checkpoint: 全部用户故事完成


Phase 9: Polish & Cross-Cutting Concerns

  • T022 后端 i18n 全量核对:脚本比对 6 个 messages*.properties 的 031 新 key 集合一致(pay.method.not.availablepay.bankcard.*、闪送支付相关),无遗漏无硬编码中文
  • T023 全量回归:mvn compile + mvn test -pl ruoyi-admin -am(含既有 60 个 OAuth/登录用例不回归)+ mapper XML python ET.parse 校验 + 行尾检查(新改文件与原风格一致)
  • T024 quickstart.md(代码级场景已随各任务单测覆盖;DDL/迁移执行与开关手测待部署后按 quickstart 验证) 六组场景走查(SC-001~006),pymysql 只读冒烟迁移覆盖 SQL(预期 0 未覆盖)
  • T025 review 子代理核对完成(2026-09-11):闸门白名单四处/缺行=开/现金放行与抢单恒可见/CARD_OMG联动/bankInfo结构与防御/确认收款幂等/DDL一致/Controller规范/存量零变化/缺省现金 全部无误:闸门调用点仅 contracts 白名单四处(grep assertUsable)、15 项决策与 spec 符合性、注释规范、存量零变化路径

Dependencies & Execution Order

Phase Dependencies

  • Phase 1(T001)→ 无依赖;Phase 2(T002-T006)→ 依赖 T001 文档定稿(DDL 与实体一致);T005 依赖 T002-T004,T006 依赖 T005;Phase 3-8 各故事→ 依赖 Phase 2 完成(闸门服务就绪)
  • 故事间:US2/US3 共用"门店→主账号"定位(T11 与 T13 抽取同一 helper);US4 独立;US5 的 T019 依赖闸门(Phase 2);US6 独立
  • Phase 9 → 依赖全部故事完成

用户故事独立测试顺序(增量交付)

  1. MVP = Phase 1+2+3(平台开关全链路)→ 停下验证
  2. +US2/US3 → 餐饮维度完整闭环,可部署
  3. +US4 → 闪送维度闭环
  4. +US5 → 迁移完成(部署前必须已手动执行 T001 的迁移 SQL,否则线下转账显示回退)
  5. +US6 → 收尾

Parallel Opportunities

  • T002/T003/T004 三个实体可并行(不同文件)
  • T008(admin-vue)与 T007(后端)可并行;T012 与 T010/T011 可并行(前端后端不同仓库)
  • T015 与 T016 可并行;T020 与 T018/T019 可并行
  • US4(闪送包)与 US2/US3(order 包)不同代码区,可并行

Notes

  • 每完成一个任务或逻辑组即 commit(test-202609v2);DDL 执行时机由开发者掌握(US5 上线前必须执行迁移)
  • 闪送既有测试(FlashDelivery*Test)不可回归;027 商家确认收款 confirmTransferPayment 与 US6 骑手确认收款并存,互不影响
  • 老 App 兜底全靠后端闸门(FR-004),任何入口不得省略 assertUsable 调用