--- 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**: 一次性落全部数据库变更文档 - [x] T001 将 031 全部 DDL 与迁移 SQL 追加到 `updatesql/sql.md`(2026-09-11 标注):新表 `payment_method_config`(uk(method_code,scope),缺行=开)、新表 `info_bank_card`、`info_user` 加 `pay_methods`/`flash_pay_methods` 两列、`flash_delivery_order` 加 `payment_status`、027 三字段迁移 INSERT...SELECT(结构见 `data-model.md` 第 1-5 节,逐条含用途注释) --- ## Phase 2: Foundational (Blocking Prerequisites) **Purpose**: 闸门服务与共享实体——所有故事的前置 **⚠️ CRITICAL**: 本阶段完成前不得开始任何用户故事 - [x] T002 [P] 新建实体 `PaymentMethodConfig` + `PaymentMethodConfigMapper`(`ruoyi-system/src/main/java/com/ruoyi/system/domain/` 与 `mapper/`,MyBatis-Plus,全字段注释,字段见 data-model.md 第 1 节) - [x] T003 [P] 新建实体 `InfoBankCard` + `InfoBankCardMapper`(同上目录,字段见 data-model.md 第 3 节;`user_id` 索引在 T001 DDL 已含) - [x] T004 [P] `InfoUser` 加 `payMethods`/`flashPayMethods` 字段(`ruoyi-system/src/main/java/com/ruoyi/system/domain/InfoUser.java`,注释注明 NULL=全部;同步其 mapper XML 的 resultMap/列清单若存在) - [x] T005 实现 `PaymentMethodGateService`(`ruoyi-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` - [x] 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),表为空时行为与现状一致 - [x] T007 [US1] 新建 `PayMethodConfigController`(`ruoyi-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) - [x] 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 语言文件,按命名空间定位插入) - [x] T009 [US1] US1 验证:编译 + 全部新单测通过 + 手测开关即时生效(开→关→开,无需重启) **Checkpoint**: MVP 达成——平台已可一键下线/恢复任意支付方式 --- ## Phase 4: User Story 2 - 商家选择支付方式 (Priority: P1) **Goal**: 商家主账号级勾选接受的方式,未设置=全部;用户结算页按可用集渲染 **Independent Test**: 商家只勾"到付"后用户结算页仅剩到付;清空勾选回落全部(SC-003 存量零变化) - [x] T010 [US2] 新建 `MerchantPayMethodController`(`ruoyi-admin/src/main/java/com/ruoyi/app/pay/`):`GET /system/merchantPayMethods`(available+就绪度+selected,NULL 展开为全部)、`PUT /system/merchantPayMethods`(写主账号行,子账号经 022 `MerchantStoreAccessService`/主账号体系定位,空数组=清空回落);值域校验组代码合法;单测(子账号写主账号、清空语义、非法组代码拒绝) - [x] T011 [P] [US2] 用户端结算页可用方式接口:`GET /system/storePayMethods`(`@Anonymous` + `@RequestParam storeId`):`pos_store → user_id → listAvailable(MERCHANT, 商家主账号)`,返回 `[{methodCode, payType, ready}]` 供 App 结算页渲染(契约已补入 `contracts/api.md` 第 7 节);单测 - [x] 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 同样被拒 - [x] T013 [US3] `UserOrderController.createOrder` 接入闸门(`ruoyi-admin/src/main/java/com/ruoyi/app/order/UserOrderController.java:261` setPayType 前调 `assertUsable(paymentMethod, MERCHANT, 商家主账号)`;商家主账号经 pos_store 反查,与 T011 同路径抽取复用);单测(被禁拒绝/放行路径/现金跳过) - [x] 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**: 骑手只接受现金 → 转账单不进其列表;未设置骑手全可见 - [x] T015 [P] [US4] 新建 `RiderFlashPayMethodController`(`ruoyi-admin/.../pay/` 或骑手包):`GET/PUT /system/riderFlashPayMethods`(token 校验 userType=2,scope=RIDER_FLASH,NULL=全部);单测 - [x] T016 [US4] 闪送下单/报价接支付方式(`ruoyi-admin/.../flashdelivery/`):`FlashDeliveryQuoteRequest`/`FlashDeliveryCreateRequest` 加可选 `payType` 字段(注释:缺省现金 4);`normalizePayType` 从透传改为 `assertUsable(payType, RIDER_FLASH, null)`(仅平台开关层,抢单前无收款方);home/quote 响应增可选方式列表 `listAvailable(RIDER_FLASH, null)`;相关 DTO 字段注释补齐;单测(非法方式拒绝、缺省现金、可选项返回) - [x] 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**: 迁移后存量商家转账信息照常展示;启用新卡付款方只见新卡 - [x] T018 [US5] 新建 `BankCardController`(`ruoyi-admin/.../pay/`):CRUD + `PUT /system/bankCard/activate/{id}`(同事务停旧卡);校验 bankName∈字典 `taiwan_bank_list`(读取先例 `InfoUserController.java:815`)、上限 10 张、仅本人卡;i18n key `pay.bankcard.*`;单测(启用停旧、越权、上限、字典校验) - [x] 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、字段名不变) - [x] 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 + 日志;重复确认幂等 - [x] T021 [US6] 闪送确认收款:`FlashDeliveryOrder` 加 `paymentStatus`(注释:0未收1已确认)+ 同步其 mapper XML;`FlashDeliveryRiderController` 增 `POST /orders/{id}/confirmPayment`(本人中单+状态≥已送达;幂等;写 `flash_delivery_order_log` operator_type=RIDER);用户/骑手订单视图返回 `paymentStatus`;单测(条件校验、幂等、日志、不阻塞状态流转) **Checkpoint**: 全部用户故事完成 --- ## Phase 9: Polish & Cross-Cutting Concerns - [x] T022 后端 i18n 全量核对:脚本比对 6 个 `messages*.properties` 的 031 新 key 集合一致(`pay.method.not.available`、`pay.bankcard.*`、闪送支付相关),无遗漏无硬编码中文 - [x] T023 全量回归:`mvn compile` + `mvn test -pl ruoyi-admin -am`(含既有 60 个 OAuth/登录用例不回归)+ mapper XML `python ET.parse` 校验 + 行尾检查(新改文件与原风格一致) - [x] T024 quickstart.md(代码级场景已随各任务单测覆盖;DDL/迁移执行与开关手测待部署后按 quickstart 验证) 六组场景走查(SC-001~006),pymysql 只读冒烟迁移覆盖 SQL(预期 0 未覆盖) - [ ] T025 派 review 子代理核对:闸门调用点仅 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 调用