|
|
@@ -0,0 +1,164 @@
|
|
|
+---
|
|
|
+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_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**: 本阶段完成前不得开始任何用户故事
|
|
|
+
|
|
|
+- [ ] T002 [P] 新建实体 `PaymentMethodConfig` + `PaymentMethodConfigMapper`(`ruoyi-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] `InfoUser` 加 `payMethods`/`flashPayMethods` 字段(`ruoyi-system/src/main/java/com/ruoyi/system/domain/InfoUser.java`,注释注明 NULL=全部;同步其 mapper XML 的 resultMap/列清单若存在)
|
|
|
+- [ ] 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`
|
|
|
+- [ ] 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] 新建 `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)
|
|
|
+- [ ] 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] 新建 `MerchantPayMethodController`(`ruoyi-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] 新建 `RiderFlashPayMethodController`(`ruoyi-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] 新建 `BankCardController`(`ruoyi-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] 闪送确认收款:`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
|
|
|
+
|
|
|
+- [ ] T022 后端 i18n 全量核对:脚本比对 6 个 `messages*.properties` 的 031 新 key 集合一致(`pay.method.not.available`、`pay.bankcard.*`、闪送支付相关),无遗漏无硬编码中文
|
|
|
+- [ ] T023 全量回归:`mvn compile` + `mvn test -pl ruoyi-admin -am`(含既有 60 个 OAuth/登录用例不回归)+ mapper XML `python ET.parse` 校验 + 行尾检查(新改文件与原风格一致)
|
|
|
+- [ ] T024 quickstart.md 六组场景走查(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 调用
|