ソースを参照

031可配置支付方式plan:research/data-model/contracts/quickstart+CLAUDE.md指针

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
qmj 8 時間 前
コミット
faf90bcb20

+ 1 - 1
CLAUDE.md

@@ -204,5 +204,5 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
 
 <!-- SPECKIT START -->
 For additional context about technologies to be used, project structure,
-shell commands, and other important information, read the current plan: `specs/029-merchant-self-delivery/plan.md`
+shell commands, and other important information, read the current plan: `specs/031-pay-method-config/plan.md`
 <!-- SPECKIT END -->

+ 114 - 0
specs/031-pay-method-config/contracts/api.md

@@ -0,0 +1,114 @@
+# API Contracts: 可配置支付方式(031-pay-method-config)
+
+**Date**: 2026-09-11 | **Spec**: [spec.md](spec.md) | **Data Model**: [data-model.md](data-model.md)
+
+所有接口遵守项目 Controller 规范:token 用 `@RequestHeader String token`,POST 业务参数用 `@RequestBody` DTO,GET 查询参数逐个 `@RequestParam`;DTO 字段不加 Bean Validation 注解,校验在 Service,错误信息走 `MessageUtils.message`。
+
+## 0. 核心内部契约:PaymentMethodGateService(ruoyi-system)
+
+单一校验入口,**唯一**闸门实现(spec FR-004)。
+
+```java
+/** 校验支付方式可用,不可用抛 ServiceException(国际化);三下单入口统一下单时调用。 */
+void assertUsable(String payType, GateScope scope, Long payeeUserId)
+
+/** 列出某维度对收款方可用的支付方式(结算页可选项、骑手列表过滤共用)。 */
+List<PayMethodOption> listAvailable(GateScope scope, Long payeeUserId)
+
+enum GateScope { MERCHANT, RIDER_FLASH }
+```
+
+调用点(**仅此三处 + bankInfo 一处**,禁止新增重复实现):
+
+| 调用方 | 位置 | 参数 |
+|---|---|---|
+| 用户餐饮下单 | `UserOrderController.createOrder`(setPayType 前后) | payType=input.paymentMethod, MERCHANT, 商家主账号 userId |
+| 商家建单 | `PosOrderController /addorder` | payType 固定 "1"(COD), MERCHANT —— COD 默认开,校验保持对称 |
+| 闪送下单 | `FlashDeliveryApplicationService.create`(normalizePayType 处) | payType, RIDER_FLASH, 无收款方(抢单前,只校验平台开关) |
+| 027 bankInfo | `ChantingStoreController.bankInfo` | OFFLINE_TRANSFER, MERCHANT, 商家 userId |
+
+错误 key(6 语言文件全配):`pay.method.not.available`(该支付方式当前不可用,请更换支付方式)。
+
+## 1. 平台端(foodie-admin-vue,权限 `pay:method:config`,操作记 @Log)
+
+### 1.1 查询开关矩阵
+
+```
+GET /system/payMethodConfig/list
+```
+
+返回:方式×维度全矩阵(含缺行按"开"补齐的虚拟行),每项 `{methodCode, methodName(i18n key), scope, enabled, sort}`。
+
+### 1.2 保存开关
+
+```
+PUT /system/payMethodConfig
+Body: { items: [ { methodCode, scope, enabled } ] }
+```
+
+批量 upsert;`@Log(title="支付方式设置")`;`@PreAuthorize("@ss.hasPermi('pay:method:config')")`。
+
+## 2. 商家端 PC(foodie-store,token = 商家主账号/子账号)
+
+### 2.1 支付方式设置页数据
+
+```
+GET /system/merchantPayMethods
+```
+
+返回:`{ available: [ {methodCode, methodName, ready} ], selected: ["COD", ...] }`——available = 平台商家维度开放集合(附就绪度标记),selected = 商家当前选择(NULL 视为全部,返回时展开为全部可用项)。
+
+### 2.2 保存选择
+
+```
+PUT /system/merchantPayMethods
+Body: { methodCodes: ["COD", "CARD_OMG"] }   // 空数组=清空=回落全部
+```
+
+写 `info_user.pay_methods`(主账号行;子账号可编辑,写其主账号——与 022 子账号权限体系一致)。
+
+### 2.3 银行卡管理(商家/骑手通用)
+
+```
+GET    /system/bankCard                    // 本人卡列表(含启用标记)
+POST   /system/bankCard                    // 新增 {bankName, accountNo, accountName}
+PUT    /system/bankCard                    // 修改 {id, bankName, accountNo, accountName}
+DELETE /system/bankCard/{id}               // 删除(启用卡删除后无启用卡→线下转账不就绪)
+PUT    /system/bankCard/activate/{id}      // 启用某卡(同事务停旧卡)
+```
+
+校验:bankName ∈ 字典 `taiwan_bank_list`;条数上限 10;只能操作本人卡。
+
+## 3. 骑手端(骑手 App 接口,本期无前端)
+
+```
+GET /system/riderFlashPayMethods           // 返回同 2.1 结构(scope=RIDER_FLASH)
+PUT /system/riderFlashPayMethods           // { methodCodes: [...] },空=回落全部
+```
+
+写入 `info_user.flash_pay_methods`;要求 token userType=2。
+
+## 4. 闪送(FlashDelivery 现有接口的增量)
+
+- **报价/下单**:`FlashDeliveryQuoteRequest` / `FlashDeliveryCreateRequest` 增加可选 `payType`;下单时经闸门校验(RIDER_FLASH,仅平台开关层);缺省=现金 4(现状不变)。
+- **用户端可见性**:报价响应/首页返回闪送维度可选支付方式列表(`listAvailable(RIDER_FLASH, null)`)。
+- **骑手抢单列表**:现有查询追加过滤——订单 payType ∈ 骑手接受集合(flash_pay_methods NULL=全部开放项)。`pickupDistanceMeters` 等既有行为不变。
+- **骑手确认收款**(新):
+
+```
+POST /system/flashDelivery/rider/orders/{id}/confirmPayment
+```
+
+条件:本人中单 + 状态已达已送达或之后;效果:payment_status 0→1 + 日志(RIDER);幂等:已确认再调返回成功。
+
+## 5. 027 兼容改造
+
+`GET /chanting/store/bankInfo?id={storeId}` —— **路径、匿名、返回结构(bankAccountName/bankName/bankAccountNo 三字段或 data=null)完全不变**:
+
+- 数据源:info_user 三字段 → `info_bank_card` 该商家 is_active=1 的卡
+- 可见性:原"data != null 才显示"→ 闸门(OFFLINE_TRANSFER 平台开 ∩ 商家接受 ∩ 存在启用卡)不满足时返回 data=null,老客户端自然隐藏
+- 老客户端提交线下转账下单(paymentMethod=6)被闸门拒绝 → 国际化提示(FR-004 兜底)
+
+## 6. i18n
+
+后端 `messages*.properties` ×6:`pay.method.not.available`、银行卡校验类 key(`pay.bankcard.*`)。前端:admin-vue 支付方式设置页、foodie-store 支付设置页全部 key 四语言(vi/zh/tw/en),命名驼峰有意义(项目规范)。

+ 84 - 0
specs/031-pay-method-config/data-model.md

@@ -0,0 +1,84 @@
+# Data Model: 可配置支付方式(031-pay-method-config)
+
+**Date**: 2026-09-11 | **Spec**: [spec.md](spec.md) | **Research**: [research.md](research.md)
+
+> 所有 DDL 追加到 `updatesql/sql.md`(标注日期与用途),由开发者手动执行,不在代码/迁移脚本中自动执行。
+
+## 1. 新表 `payment_method_config`(平台支付方式开关)
+
+| 列 | 类型 | 说明 |
+|---|---|---|
+| id | BIGINT AUTO_INCREMENT PK | |
+| method_code | VARCHAR(32) NOT NULL | 支付方式代码:COD / CARD_OMG / LINE_PAY / OFFLINE_TRANSFER(**组代码**,非 payType 数值) |
+| scope | VARCHAR(32) NOT NULL | 维度:MERCHANT(商家餐饮单)/ RIDER_FLASH(骑手闪送) |
+| enabled | TINYINT NOT NULL DEFAULT 1 | 1=开 0=关 |
+| sort | INT NOT NULL DEFAULT 0 | 平台设置页展示排序 |
+| update_by | BIGINT | 最后操作管理员 |
+| update_time / create_time | DATETIME | |
+
+- **唯一键**:`uk(method_code, scope)`
+- **缺行语义 = 开启**:表为空时系统行为与现状完全一致;平台页保存时才 upsert 行;新增支付方式代码无需初始化数据。
+- 方式×维度矩阵初始仅需可配的行;`CARD_OMG` 组 = payType 2+5,开关联动;`COD` = payType 1;`LINE_PAY` = 3;`OFFLINE_TRANSFER` = 6。**现金(4) 不建行不纳入。**
+- RIDER_FLASH 维度可配集合:COD / LINE_PAY / OFFLINE_TRANSFER(闪送无线上卡支付)。
+
+## 2. `info_user` 加两列(收款方选择)
+
+| 列 | 类型 | 说明 |
+|---|---|---|
+| pay_methods | VARCHAR(128) NULL | 商家接受的支付方式组代码 CSV(如 `COD,CARD_OMG`);**NULL/空 = 全部启用**(存量零变化) |
+| flash_pay_methods | VARCHAR(128) NULL | 骑手接受的闪送收款方式 CSV;**NULL/空 = 平台开放的全部** |
+
+- 商家主账号级语义:读写在 `info_user` 主账号行;查询时连锁门店经 `pos_store.user_id` 定位主账号(与 027 bankInfo 同路径)。
+- 保存时服务端校验值域 ∈ 平台该维度当前开放集合之外仍可保存(平台后关不影响已存值,实时按闸门算),但**必须**是组合法代码。
+
+## 3. 新表 `info_bank_card`(收款银行卡)
+
+| 列 | 类型 | 说明 |
+|---|---|---|
+| id | BIGINT AUTO_INCREMENT PK | |
+| user_id | BIGINT NOT NULL | 归属(商家主账号 / 骑手),索引 |
+| bank_name | VARCHAR(64) NOT NULL | 银行名称(字典 `taiwan_bank_list` 中文名) |
+| account_no | VARCHAR(64) NOT NULL | 银行账号 |
+| account_name | VARCHAR(64) NOT NULL | 户名 |
+| is_active | TINYINT NOT NULL DEFAULT 0 | 1=启用;**同一 user 同时最多一张启用**(服务层事务保证:启用新卡同事务停旧卡) |
+| create_time / update_time | DATETIME | |
+
+- 商家/骑手通用,不做 user_type 冗余(归属即 info_user)。
+- 不加分行栏位(Assumption,可后补)。
+
+## 4. `flash_delivery_order` 加一列(收款状态)
+
+| 列 | 类型 | 说明 |
+|---|---|---|
+| payment_status | TINYINT NOT NULL DEFAULT 0 | 0=未收款 1=骑手已确认收款 |
+
+- **支付方式复用现有 `pay_type` 列**(值域扩展为平台闪送维度开放集合,当前已存 4/6),不新增支付方式列。
+- 确认收款仅更新 payment_status + 写 `flash_delivery_order_log`(operator_type=RIDER),不改订单状态,不阻塞送达/完成。
+
+## 5. 027 存量迁移(一次性 SQL,入 updatesql/sql.md)
+
+```sql
+-- 031 027银行三字段迁移为启用卡
+INSERT INTO info_bank_card (user_id, bank_name, account_no, account_name, is_active, create_time)
+SELECT user_id, bank_name, bank_account_no, bank_account_name, 1, NOW()
+FROM info_user
+WHERE user_id IS NOT NULL AND bank_name IS NOT NULL AND bank_name <> ''
+  AND bank_account_no IS NOT NULL AND bank_account_no <> ''
+  AND bank_account_name IS NOT NULL AND bank_account_name <> '';
+```
+
+- 迁移后 `bankInfo` 改读 `info_bank_card is_active=1`,**info_user 旧三字段保留不删**(App 端商家审核资料等仍引用)。
+- 就绪度判据同步切换:`OFFLINE_TRANSFER` 就绪 = 存在启用卡。
+
+## 6. 就绪度判定(不落表,服务层实现)
+
+| 方式 | 就绪条件(门店/收款方) |
+|---|---|
+| CARD_OMG | 门店 `pos_store_omg` 存在启用凭证 |
+| LINE_PAY | 门店 `pos_store_line_pay` 存在启用凭证 |
+| OFFLINE_TRANSFER | 收款方 `info_bank_card` 存在 is_active=1 的卡 |
+| COD | 无就绪度要求(恒满足) |
+
+## 7. 实体/映射层级
+
+按全栈字段清单:ruoyi-system `domain`(新实体 PaymentMethodConfig / InfoBankCard + InfoUser / FlashDeliveryOrder 加字段)→ mapper(MyBatis-Plus BaseMapper + 必要自定义 SQL)→ ruoyi-admin service/controller → 前端。resultMap 如涉及 XML 同步(本表预期纯 MyBatis-Plus,无 XML)。

+ 81 - 0
specs/031-pay-method-config/plan.md

@@ -0,0 +1,81 @@
+# 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 端页面。

+ 65 - 0
specs/031-pay-method-config/quickstart.md

@@ -0,0 +1,65 @@
+# Quickstart: 可配置支付方式(031-pay-method-config)
+
+**Date**: 2026-09-11 | **Spec**: [spec.md](spec.md) | **Contracts**: [contracts/api.md](contracts/api.md)
+
+验证场景按 spec Success Criteria 展开。环境要求:本地编译用 JDK21(`C:\Users\qmj\.jdks\graalvm-jdk-21.0.7` 临时 JAVA_HOME);数据库变更已手动执行 `updatesql/sql.md` 中 031 各条;本地 Redis 可用(起服务时)。
+
+## 0. 编译与单测
+
+```bash
+export JAVA_HOME="C:\Users\qmj\.jdks\graalvm-jdk-21.0.7" && export PATH="$JAVA_HOME/bin:$PATH"
+mvn compile -q -DskipTests        # 编译
+mvn test -pl ruoyi-admin -am -Dtest='PaymentMethodGate*,PayMethod*,BankCard*,FlashDelivery*' -Dsurefire.failIfNoSpecifiedTests=false
+```
+
+重点单测(对照 FR):
+- 闸门服务:平台关/商家未选/不就绪 三独立原因都拒绝;缺行=开;CARD_OMG 组 2+5 联动;COD 默认开;现金不校验
+- 三下单入口:各传被禁方式 → `pay.method.not.available` 国际化消息
+- 抢单过滤:骑手只接受现金 → 银行转账单不出现;NULL=全部
+- 银行卡:启用新卡停旧卡(事务);删除启用卡后线下转账不就绪
+- 迁移:三字段齐全才生成启用卡
+
+## 1. 平台开关即时生效(SC-001)
+
+1. 平台端"支付方式设置"页:关闭 `LINE_PAY × MERCHANT`
+2. 用户端任意商家结算页刷新 → LINE Pay 消失
+3. 重新开启 → 恢复(无重启)
+4. `LINE_PAY × RIDER_FLASH` 保持开 → 闪送下单仍可选 LINE Pay(维度独立交叉验证)
+
+## 2. 下单校验堵漏(SC-002)
+
+用 curl 直连(绕过前端),向 `/system/order/createOrder` 提交 `paymentMethod=3`(平台已关 LINE_PAY):
+
+- 预期:HTTP 200 但 code=500,msg 为所请求语言的"该支付方式当前不可用"
+- zh-TW / en / vi 各请求一次(Accept-Language 或用户语言上下文),文案随语言切换
+
+## 3. 存量零回归(SC-003)
+
+1. 升级部署后不执行任何设置操作
+2. 抽样存量商家下单(各支付方式)、商家建单(到付)、闪送下单(现金/转账)
+3. 行为与升级前一致;`SELECT COUNT(*) FROM payment_method_config` 可为 0(缺行=开)
+4. 抽样骑手(未设置 flash_pay_methods)抢单列表 → 全部平台开放方式的订单可见
+
+## 4. 027 迁移不回退(SC-004)
+
+```sql
+-- 迁移后核对(测试库只读冒烟,pymysql)
+SELECT COUNT(*) FROM info_user
+WHERE bank_name<>'' AND bank_account_no<>'' AND bank_account_name<>''
+  AND user_id NOT IN (SELECT user_id FROM info_bank_card WHERE is_active=1);
+-- 预期 0(三字段齐全者 100% 迁移为启用卡)
+```
+
+再调 `GET /chanting/store/bankInfo?id={已迁移商家门店}` → 三字段值与迁移前一致;平台关 OFFLINE_TRANSFER×MERCHANT 再调 → data=null(老客户端自然隐藏)。
+
+## 5. 抢单过滤与确认收款(SC-005 / FR-013)
+
+1. 骑手 A 设置只接受 `COD`;用户创建 LINE Pay 闪送单
+2. 骑手 A 新任务列表无此单;骑手 B(未设置)可见
+3. 骑手 B 接单→取件→送达(不确认收款),用户确认收货 → 订单正常完成,收款状态=未收
+4. 骑手 B 补确认收款 → payment_status=1,flash_delivery_order_log 增 RIDER 操作行;重复调用幂等成功
+
+## 6. 前端页面冒烟
+
+- admin-vue 支付方式设置页:矩阵渲染、开关切换、@Log 入库、四语言切换无 key 裸奔
+- foodie-store 支付设置页:勾选保存→再进回显;银行卡增删改启用;四语言

+ 81 - 0
specs/031-pay-method-config/research.md

@@ -0,0 +1,81 @@
+# Research: 可配置支付方式(031-pay-method-config)
+
+**Date**: 2026-09-11 | **Spec**: [spec.md](spec.md)
+
+本文记录实现前核对的代码现状(含行号锚点)与既定决策,纠正旧记忆中过时的事实。
+
+## 一、代码现状(2026-09-11 核对)
+
+### 1.1 payType 值域与三个下单入口
+
+| 值 | 含义 | 备注 |
+|---|---|---|
+| 1 | 到付 | 商家建单固定用它(`PosOrderController.java:266/1226` `setPayType("1")`) |
+| 2 | 信用卡(OMG) | `OrderLifecycleService.isCardWalletPayType`(`PosOrderController.java:668`) |
+| 3 | LINE Pay | `LinePayService.PAY_TYPE_LINE`(`PosOrderController.java:676`) |
+| 4 | 现金 | 仅商家建单场景;闪送也已用作"现金" |
+| 5 | Apple Pay(OMG) | 与 2 同为 OMG 渠道 |
+| 6 | 线下转账 | 027 引入 |
+
+- **漏洞确认**:`UserOrderController.java:261` `posOrder.setPayType(input.getPaymentMethod())` —— 用户下单传什么存什么,**零校验**。
+- 商家建单(`/addorder`)固定到付 1,无支付方式选择。
+
+### 1.2 闪送现状(⚠️ 纠正旧记忆"闪送零支付代码")
+
+闪送**已有**支付字段与逻辑(近期提交"闪送订单增加支付方式字段"+"修改normalizePayType"):
+- `FlashDeliveryOrder.payType` 已存在(`FlashDeliveryOrder.java`,注释:4=现金、6=线下转账)
+- `FlashDeliveryApplicationService.normalizePayType(...)` 当前为**透传**(白名单校验被注释掉),缺省默认现金
+- `FlashDeliveryOrderView.payType`、骑手列表 `FlashDeliveryRiderOrderListView.payType` 均已返回
+- 闪送**没有**:payment_status(收款状态)、下单闸门校验、骑手收款方式设置
+
+### 1.3 027 线下转账(迁移基准)
+
+- 接口:`GET /chanting/store/bankInfo?id={storeId}`(匿名)——`pos_store.id → user_id → info_user`,返回 **bankAccountName / bankName / bankAccountNo** 三字段;客户端 `data != null` 才显示线下支付(详见 `specs/027-offline-transfer-payment/contracts/api.md`)
+- 商家确认收款:`/system/orderShOprate/confirmTransferPayment`(**商家**确认,订单级;031 的骑手确认收款是闪送订单级,二者并存不冲突)
+- `InfoUser` 银行相关字段:`bankAccount`(185)、`bankPhoto`(193)、`bankAccountName`(197)、`bankName`(199)、`bankAccountNo`(205)。027 转账展示用的三字段 = `bankName + bankAccountNo + bankAccountName`。
+
+### 1.4 凭证与就绪度数据源
+
+- OMG(信用卡/Apple Pay):门店级 `pos_store_omg`(`/system/storeOmg` 管理)
+- LINE Pay:门店级 `pos_store_line_pay`
+- admin-vue 已有门店支付凭证配置页:`foodie-admin-vue/src/views/mendian/storePayment`(OmgTab/LinePayTab)——031 的平台开关页是**独立新页**(方式×维度开关),不复用该页
+
+### 1.5 可复用先例
+
+- 银行字典:`taiwan_bank_list` 已存在,且有读取先例(`InfoUserController.java:815-821`,`query.setDictType("taiwan_bank_list")`)
+- 商家 PC 设置页先例:`foodie-store/src/views/SelfDeliverySettings.vue`(029)
+- 平台开关先例:008 用 `sys_config` 单 key;**本期改用结构化表**(已决策),理由:方式×维度矩阵、带排序、可逐行改
+- 模块边界:校验服务需读 `info_user`/`pos_store_omg`/`pos_store_line_pay`/`info_bank_card`(全在 ruoyi-system)→ 闸门服务放 **ruoyi-system**,Controller 调用在 ruoyi-admin(符合 admin→system 单向依赖)
+
+### 1.6 前端项目
+
+- 平台端:`E:\QtwCode\foodie\foodie-admin-vue`(新页"支付方式设置",四语言)
+- 商家端:`E:\QtwCode\foodie\foodie-store`(新页"支付设置"= 方式勾选 + 银行卡管理,vue-i18n 四语言 `src/lang/`)
+- 两项目文件均为 CRLF,编辑用 Python 脚本(项目惯例)
+
+## 二、既定决策(不再讨论,实现依据)
+
+来源:2026-09-09 对话定稿 13 项 + 2026-09-11 补充 2 项,全文见 spec.md。
+
+1. 三层闸门:可用 = 平台开关(方式×维度) ∩ 收款方选择 ∩ 就绪度;维度 = MERCHANT(商家餐饮单) / RIDER_FLASH(骑手闪送),独立开关可交叉
+2. 闪送资金用户直达骑手,平台不经手;支付方式**下单时选**并随订单固定
+3. 骑手未设置 flash_pay_methods = 平台开放全部;商家 pay_methods NULL = 全部启用(存量零变化)
+4. 商家选择在**主账号级**(连锁共享,与 027 银行信息语义一致)
+5. 信用卡+Apple Pay 同组一个开关(OMG 渠道,023 同组先例)
+6. 到付(1) 纳入管控默认开;现金(4) 不纳入(仅商家建单)
+7. 不做平台逐店覆盖(YAGNI)
+8. 老 App:后端拒绝+国际化提示;`bankInfo` 接口保留、结构不变、改读启用卡、套闸门
+9. 商家 PC 做设置页;商家 App/骑手 App 只出接口(027 FR-010 模式)
+10. 银行卡列表化:`info_bank_card` 挂 info_user,商家/骑手通用,多卡同时仅一张 is_active,启用新卡停旧卡;付款方只见启用卡
+11. 027 迁移:info_user 三字段存量 `INSERT...SELECT` 迁成一张启用卡;就绪度判据从"三字段齐全"改"存在启用卡"
+12. 骑手确认收款:闪送订单 paymentStatus 0→1 + 操作日志;不阻塞送达/完成
+13. **下单闸门校验封装为单一独立方法**(唯一校验入口),三下单路径(用户餐饮下单/商家建单/闪送下单)统一下单时调用,禁止各入口重复实现
+14. DDL 全部追加 `updatesql/sql.md`,开发者手动执行
+15. 平台开关缺行语义 = 开启(零数据依赖,新方式代码上线即默认可用,平台页保存时才落行,enabled=0 才是关)
+
+## 三、实现要点结论
+
+- 闪送支付方式**复用现有 `pay_type` 列**扩展值域(不新增列);仅新增 `payment_status`
+- `normalizePayType` 透传行为改为调用闸门校验方法(补回被注释掉的白名单 + 三层闸门)
+- `bankInfo` 返回结构与字段名完全不变,仅数据源与可见性判断变化
+- 闸门服务命名建议 `PaymentMethodGateService`(ruoyi-system),提供 `assertUsable(...)`(校验,抛国际化异常)与 `listAvailable(...)`(结算页可选项)两个核心方法