# Implementation Plan: 可配置支付方式(031-pay-method-config) **Branch**: `test-202609v2`(直接开发,不建 feature 分支) | **Date**: 2026-09-11 | **Spec**: [spec.md](spec.md) **Input**: Feature specification from `/specs/031-pay-method-config/spec.md` ## Summary 支付方式可用性从代码固定行为改为**三层闸门**:平台开关(方式×维度,新表 `payment_method_config`,缺行=开)∩ 收款方选择(`info_user.pay_methods` / `flash_pay_methods`,NULL=全部)∩ 就绪度(凭证/启用卡)。核心是 **ruoyi-system 的 `PaymentMethodGateService` 单一校验入口**,用户餐饮下单、商家建单、闪送下单、027 bankInfo 四处统一调用,顺带堵掉 `createOrder` 不校验 payType 的漏洞。银行卡从 027 三字段列表化为 `info_bank_card`(多卡单启用,存量 INSERT...SELECT 迁移),闪送订单复用 `pay_type` 列并新增 `payment_status`(骑手确认收款,不阻塞流程)。前端两页:admin-vue 平台开关矩阵页、foodie-store 商家支付设置页(勾选+银行卡管理),均四语言。 ## Technical Context **Language/Version**: Java 21(后端);Vue2 + Element UI(两前端) **Primary Dependencies**: Spring Boot、MyBatis-Plus、vue-i18n(foodie-store);httpclient4+fastjson2(不涉及新外部调用) **Storage**: MySQL(2 张新表 + 3 处加列,DDL 全部入 `updatesql/sql.md` 手动执行) **Testing**: JUnit5 + Mockito(后端单测);SQL 只读冒烟用 pymysql 直连测试库 **Target Platform**: Linux 服务端(Spring Boot)+ 两个管理前端(Windows 开发) **Project Type**: web-service(多模块 Maven + 两前端仓库) **Performance Goals**: 开关读取走字典式缓存(同 `DictUtils` 模式),结算页/下单路径不新增可感知延迟;保存开关后下一次查询即生效 **Constraints**: 模块依赖 ruoyi-admin → ruoyi-system 单向;闸门服务放 ruoyi-system;Controller 规范(@RequestHeader/@RequestBody DTO/@RequestParam,DTO 无 Bean Validation);前端 CRLF 用 Python 编辑;Java 注释禁 `*/` **Scale/Scope**: 后端 2 新表 + 3 加列 + 1 新服务 + 6 组新接口 + 3 入口接入;前端 2 新页面;迁移 1 条 SQL;i18n 后端 6 文件 + 前端 4 文件 ## Constitution Check `.specify/memory/constitution.md` 为未填充模板占位,无已批准的项目原则约束 → **GATE: PASS(无适用条款)**。项目实际约束以 CLAUDE.md(模块边界/Controller 规范/i18n/DDL 流程)为准,已并入 Technical Context Constraints。 ## Project Structure ### Documentation (this feature) ```text specs/031-pay-method-config/ ├── plan.md # 本文件 ├── research.md # 代码现状核对(含行号锚点)与 15 项既定决策 ├── data-model.md # 表结构/迁移/就绪度判定 ├── quickstart.md # 验证场景(对照 SC-001~006) ├── contracts/api.md # 接口契约(含闸门服务内部契约与三入口调用点) ├── checklists/requirements.md └── tasks.md # /speckit-tasks 生成(待) ``` ### Source Code (repository root) ```text ruoyi-system/src/main/java/com/ruoyi/system/ ├── domain/PaymentMethodConfig.java # 新:平台开关 ├── domain/InfoBankCard.java # 新:银行卡 ├── domain/InfoUser.java # 加 payMethods/flashPayMethods ├── domain/flash/FlashDeliveryOrder.java # 加 paymentStatus ├── mapper/PaymentMethodConfigMapper.java # 新 ├── mapper/InfoBankCardMapper.java # 新 └── service/PaymentMethodGateService.java # 新:单一闸门入口(assertUsable/listAvailable) ruoyi-admin/src/main/java/com/ruoyi/app/ ├── pay/PayMethodConfigController.java # 新:平台开关(list/put) ├── pay/BankCardController.java # 新:银行卡 CRUD+启用 ├── user/... MerchantPayMethodController.java # 新:商家选择(get/put) ├── qs(RiderFlashPayMethodController).java # 新:骑手闪送方式(get/put) ├── order/UserOrderController.java # 改:createOrder 接闸门(:261) ├── order/PosOrderController.java # 改:/addorder 接闸门(COD对称校验) ├── chanting/... bankInfo 数据源改 info_bank_card + 套闸门 └── flashdelivery/service/FlashDeliveryApplicationService.java # 改:normalizePayType→闸门;抢单过滤;confirmPayment E:\QtwCode\foodie\foodie-admin-vue\src\views\pay\methodConfig\ # 新:平台开关矩阵页 E:\QtwCode\foodie\foodie-store\src\views\PayMethodSettings.vue # 新:商家支付设置页(参照SelfDeliverySettings.vue) + 两前端路由/菜单/i18n(四语言) ``` **Structure Decision**: 闸门与凭证/卡读取同层(ruoyi-system),Controller 全在 ruoyi-admin;前端独立仓库各一页。pay 包为新建(现有 chanting/store 等包无支付方式配置归属)。 ## Complexity Tracking 无 Constitution 违规需要豁免。设计保持在"1 服务 + 2 表 + 2 页"最小面:平台开关用结构化表而非逐 key sys_config(矩阵+排序+唯一约束,已决策);不引入规则引擎、不做逐店覆盖、不做 App 端页面。 ## 变更记录 - **2026-10-09 商家支付方式备注(US9)**:`payment_method_config.remark VARCHAR(500) NULL`,仅 `MERCHANT` 使用。MyBatis-Plus `BaseMapper` 自动映射,无自定义 XML。Entity/平台保存 DTO/平台读写接口增加备注及长度、维度校验;省略/null 保留,空字符串清空。闸门服务增加独立 `listMerchantRemarks()`,商家设置 GET 返回 `remarks`(组代码→非空文本),不修改共用 `PayMethodOption`,保证骑手/用户结算接口不增加说明字段。平台页可编辑,商家/摊位页只读普通插值展示并保留换行,两端新增固定文案四语言。SQL 只写 `updatesql/sql.md`,部署前开发者执行。验证配置读写、清空/兼容旧请求、长度/维度隔离、摊位一致性及前端 payload/渲染。 - **2026-09-30 夜市摊位支付设置(US8)**:普通商家继续使用 `info_user.pay_methods` 与用户归属银行卡;摊位新增 `pos_store.pay_methods`(NULL=全部)和 `info_bank_card.store_id`(摊位卡,`user_id` 为空)。摊位主仅凭有效登录账号的 `store_id` 解析摊位,校验账号启用、未删除、摊位未删除且 `is_stall=1`;夜市主无摊位设置权限。闸门在 MERCHANT 维度根据 `store_id` 判断摊位归属,支付方式选择和线下转账就绪度分别读摊位字段和摊位启用卡;`bankInfo` 同一归属规则。商家端复用支付设置页并向摊位主显示菜单。通用门店/摊位 JSON 不可改 `payMethods` 和 `isStall`,`editStall` 校验夜市主归属并固定支付路由字段。摊位卡新增和启用在事务内先以 `pos_store` 行锁串行化,避免多位摊位主并发产生两张首启用卡。SQL 仅登记在 `updatesql/sql.md`,由开发者手工执行后再部署代码。验证:访问隔离、同摊位共享、跨摊位隔离、结算/下单闸门与 `bankInfo`,并回归普通商家和骑手。 - **2026-09-20 自取/堂食现金支付(spec US7)**:现金(payType=4)从"完全不管控、仅商家建单"调整为"纳入商家勾选(info_user.pay_methods 新增 CASH 组)、仍无平台开关与就绪度";可用场景扩展到用户自取/堂食下单(外送单直传现金被闸门拒绝)。闸门新增带 orderType 上下文的 `assertUsableForOrder` 重载,原四处调用点行为不变。无 DDL。任务见 tasks.md Phase 10(T026-T030)。 - **2026-09-28 商家PC联调修复与 DDL 补登记**:商家端联调暴露四项问题当日修复——①foodie-store 侧边菜单缺「支付设置」入口(T031);②未配置商家(NULL)展示为全不勾,改为默认全选展示对齐「NULL=接受全部」口径(T032);③银行名称下拉误调不存在的 `/system/bankList`,改调 `/infouser/user/bankList`(T033);④保存 SQL 写入 info_user 不存在的 update_time 列致 Unknown column,商家/骑手两处移除(T034)。同日补登记 031 全套 DDL 至 updatesql/sql.md(原误只写在 data-model.md 从未入库,已执行测试库并完成 027 存量卡迁移);清理 6 文件 17 处无效 `@Auth(session=true)` 标注(947867e)。部署链提醒:商家PC 测试站须以 .env.test 构建(曾误用生产 API 基址 api.awayqtw.com 导致 401 与行为漂移)。 - **2026-10-10 骑手 LINE Pay**:PaymentMethodGateService 在 RIDER_FLASH 且 payeeUserId 已知时读取 IInfoRiderLinePayService.getEnabledCurrent;用户未确定骑手仍服从平台开关。待抢列表复用 listAvailable,接单新增同闸门 assertUsable,金额/收款人与凭证由 019 流程在服务端确定。新增凭证/SQL/UI由 019 管理,031 不新增字段或 App 改动,现金转账及商家就绪规则回归。