Browse Source

docs: 031规格新增自取堂食现金支付(US7)——现金纳入商家勾选,外送拒现金

qmj 6 days ago
parent
commit
e174326d91

+ 19 - 4
specs/031-pay-method-config/contracts/api.md

@@ -29,6 +29,19 @@ enum GateScope { MERCHANT, RIDER_FLASH }
 
 错误 key(6 语言文件全配):`pay.method.not.available`(该支付方式当前不可用,请更换支付方式)。
 
+### 0.1 现金(CASH,payType=4)闸门增量(2026-09-20 变更,spec US7/FR-006)
+
+现金不设平台开关行、无就绪度,闸门判定需要**订单类型上下文**:
+
+```java
+/** 带订单类型的校验重载:用户餐饮下单调用;其余入口沿用原签名(商家建单/闪送现金按现状放行)。 */
+void assertUsableForOrder(String payType, GateScope scope, Long storeId, Long payeeUserId, Long orderType)
+```
+
+- `payType=4`:`orderType∈{1自取,2堂食}` → 仅查商家勾选(`info_user.pay_methods` 含 `CASH`,或 NULL=全部接受)→ 通过;`orderType=0外送` → 拒绝(`pay.method.not.available`);`orderType=null`(商家建单/闪送)→ 放行(现状)。
+- 商家勾选保存(§2.2)值域校验 `groupsOf(MERCHANT)` 扩展含 `CASH`;`allGroups()`(平台矩阵)**不含** CASH。
+- 结算页 §7 `listAvailable` 增加可选 `orderType` 入参:`type∈{1,2}` 且商家接受时追加 `{methodCode:"CASH", payType:"4", ready:true}`;`orderType` 缺省不追加(老客户端零变化)。
+
 ## 1. 平台端(foodie-admin-vue,权限 `pay:method:config`,操作记 @Log)
 
 ### 1.1 查询开关矩阵
@@ -56,13 +69,13 @@ Body: { items: [ { methodCode, scope, enabled } ] }
 GET /system/merchantPayMethods
 ```
 
-返回:`{ available: [ {methodCode, methodName, ready} ], selected: ["COD", ...] }`——available = 平台商家维度开放集合(附就绪度标记),selected = 商家当前选择(NULL 视为全部,返回时展开为全部可用项)。
+返回:`{ available: [ {methodCode, methodName, ready} ], selected: ["COD", ...] }`——available = 平台商家维度开放集合(附就绪度标记)**追加 CASH 项(2026-09-20 变更:无平台开关、ready 恒 true,是否可用由商家勾选决定)**,selected = 商家当前选择(NULL 视为全部,返回时展开为全部可用项含 CASH)。
 
 ### 2.2 保存选择
 
 ```
 PUT /system/merchantPayMethods
-Body: { methodCodes: ["COD", "CARD_OMG"] }   // 空数组=清空=回落全部
+Body: { methodCodes: ["COD", "CARD_OMG"] }   // 空数组=清空=回落全部;值域含 "CASH"(2026-09-20 变更)
 ```
 
 写 `info_user.pay_methods`(主账号行;子账号可编辑,写其主账号——与 022 子账号权限体系一致)。
@@ -116,10 +129,12 @@ POST /system/flashDelivery/rider/orders/{id}/confirmPayment
 ## 7. 用户端结算页可用支付方式(App 渲染依据)
 
 ```
-GET /system/storePayMethods?storeId={storeId}
+GET /system/storePayMethods?storeId={storeId}&type={type}    // type 可选(2026-09-20 变更):1自取/2堂食
 ```
 
 - 鉴权:匿名(`@Anonymous`,同 bankInfo 风格);App 结算页进入时调用,按返回渲染支付方式列表。
-- 逻辑:`pos_store.id → user_id(商家主账号)→ listAvailable(MERCHANT, 主账号)`。
+- 逻辑:`pos_store.id → user_id(商家主账号)→ listAvailable(MERCHANT, 主账号, orderType)`。
 - 返回:`data: [ { methodCode, payType, ready } ]`——methodCode 组代码 / payType 对应数值(CARD_OMG 展开 2 与 5 两项)/ ready 就绪度。线下转账 ready=false 时不渲染(替代 027 时代"bankInfo!=null 才显示"的判断,App 可统一按本接口渲染)。
+- **现金项(2026-09-20 变更)**:`type=1 或 2` 且商家接受现金(勾选含 CASH 或未设置)时,追加 `{methodCode:"CASH", payType:"4", ready:true}`;`type=0 外送` 或不传 `type` 不返回现金(老客户端零变化)。
 - 与 bankInfo 关系:本接口管"选项显隐",bankInfo 仍管"转账信息内容"(结构不变,第 5 节)。
+- **现金下单语义**:现金仅接受 `type∈{1,2}`;订单创建后 payStatus 置 1(同到付线下收款语义,直接进入"进行中",不经"待付款");`type=0` 直传现金被闸门拒绝(`pay.method.not.available`)。

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

@@ -82,3 +82,12 @@ WHERE user_id IS NOT NULL AND bank_name IS NOT NULL AND bank_name <> ''
 ## 7. 实体/映射层级
 
 按全栈字段清单:ruoyi-system `domain`(新实体 PaymentMethodConfig / InfoBankCard + InfoUser / FlashDeliveryOrder 加字段)→ mapper(MyBatis-Plus BaseMapper + 必要自定义 SQL)→ ruoyi-admin service/controller → 前端。resultMap 如涉及 XML 同步(本表预期纯 MyBatis-Plus,无 XML)。
+
+## 8. 现金(CASH)增量语义(2026-09-20 变更,US7)
+
+**无表结构变更。** 现金可用性不落新表/新字段:
+
+- 商家勾选:复用 `info_user.pay_methods`(MERCHANT 维度 CSV),值域新增组代码 `CASH`;NULL/空=全部接受(含现金),存量商家行为零变化。
+- 无 `payment_method_config` 行:现金无平台开关(`allGroups()` 不含 CASH,平台矩阵页不渲染)。
+- 无就绪度要求(同 COD,恒满足)。
+- 订单侧:`pos_order.pay_type=4` 与 `type∈{1,2}` 组合即现金自取/堂食单,`pay_status=1`(同到付线下收款语义),无新字段。

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

@@ -79,3 +79,7 @@ E:\QtwCode\foodie\foodie-store\src\views\PayMethodSettings.vue   # 新:商家支
 ## Complexity Tracking
 
 无 Constitution 违规需要豁免。设计保持在"1 服务 + 2 表 + 2 页"最小面:平台开关用结构化表而非逐 key sys_config(矩阵+排序+唯一约束,已决策);不引入规则引擎、不做逐店覆盖、不做 App 端页面。
+
+## 变更记录
+
+- **2026-09-20 自取/堂食现金支付(spec US7)**:现金(payType=4)从"完全不管控、仅商家建单"调整为"纳入商家勾选(info_user.pay_methods 新增 CASH 组)、仍无平台开关与就绪度";可用场景扩展到用户自取/堂食下单(外送单直传现金被闸门拒绝)。闸门新增带 orderType 上下文的 `assertUsableForOrder` 重载,原四处调用点行为不变。无 DDL。任务见 tasks.md Phase 10(T026-T030)。

+ 25 - 5
specs/031-pay-method-config/spec.md

@@ -16,7 +16,7 @@
 
 - **维度**两个,独立开关、可交叉:**商家餐饮单维度**(用户向商家付款的订单)、**骑手闪送维度**(用户直接付骑手的闪送订单)。例如"线下转账"可以商家侧关、骑手侧开。
 - **资金流不变**:闪送款项用户直接付骑手(现金 / LINE Pay 好友转账 / 银行转账),平台不经手。
-- 支付方式取值域(与现有系统一致):1=到付、2=信用卡(OMG)、3=LINE Pay、4=现金(仅商家建单场景)、5=Apple Pay(OMG)、6=线下转账。
+- 支付方式取值域(与现有系统一致):1=到付、2=信用卡(OMG)、3=LINE Pay、4=现金(商家建单场景 + 用户自取/堂食下单,受商家勾选管控但无平台开关,见 FR-006)、5=Apple Pay(OMG)、6=线下转账。
 
 ## User Scenarios & Testing *(mandatory)*
 
@@ -51,6 +51,7 @@
 2. **Given** 商家只勾选"到付、现金",**When** 用户下单,**Then** 结算页仅展示到付、现金(仍受平台开关约束)。
 3. **Given** 连锁商家主账号修改支付方式选择,**When** 任意门店视角查看,**Then** 配置一致(主账号级共享)。
 4. **Given** 平台后续关闭了商家已勾选的某方式,**When** 用户下单,**Then** 该方式仍不可见(平台开关优先)。
+5. **Given** 商家勾选了「现金」,**When** 用户创建自取/堂食订单,**Then** 结算页出现现金选项(2026-09-20 变更,见 US7);商家取消勾选后自取/堂食不再出现现金,外送订单始终不受勾选影响(现金本就不可用于外送)。
 
 ---
 
@@ -119,13 +120,31 @@
 
 ---
 
+### User Story 7 - 自取/堂食订单支持现金支付 (Priority: P1)(2026-09-20 新增)
+
+用户创建自取(type=1)或堂食(type=2)订单时,可选择现金支付;到店后线下付款,订单不经过在线支付环节。现金可用性 = **商家勾选**(含未设置默认接受),**无平台开关、无就绪度**(现金无需凭证)。外送订单(type=0)不可选现金——绕过界面直传被后端拒绝并返回国际化提示;商家建单与闪送缺省现金保持现状(均不套用本管控)。现金自取/堂食订单创建后不进入「待付款」,与到付同理直接进入「进行中」,由商家线下收款后按既有流程履约。
+
+**Why this priority**: 用户明确提出的新需求;复用 031 已有的商家勾选与闸门骨架,增量小。
+
+**Independent Test**: 商家勾选现金后,用户自取下单选择现金成功且订单直接进入进行中;外送单直传现金被拒;商家取消勾选后自取/堂食不再出现现金。
+
+**Acceptance Scenarios**:
+
+1. **Given** 商家勾选「现金」(或从未设置),**When** 用户创建自取/堂食订单并选现金,**Then** 下单成功,订单不进入待付款页签、直接可见于进行中。
+2. **Given** 商家取消勾选「现金」,**When** 用户在其自取/堂食结算页查询可用支付方式,**Then** 不返回现金项;绕过界面直传现金下单被拒绝并返回所用语言提示。
+3. **Given** 用户创建外送订单(type=0),**When** 直传现金支付方式,**Then** 后端拒绝并返回国际化提示(老客户端兜底,不静默失败)。
+4. **Given** 现金自取/堂食订单存在,**When** 用户取消或商家按既有流程处理,**Then** 与到付订单行为一致,无在线退款环节。
+
+---
+
 ### Edge Cases
 
 - 平台关闭某方式时,已存在但未支付的旧订单如何处理?→ 下单校验(US3)在创建时点生效;已创建订单不追溯改动。
 - 商家清空全部支付方式勾选?→ 视同未设置,回落为"全部启用"(与 NULL 语义一致,防止商家把自己锁死)。
 - 骑手维度关闭平台某方式后,历史闪送订单的支付方式字段?→ 记录的是下单时点值,不追溯。
 - 信用卡与 Apple Pay:作为同一组(OMG 渠道)一个开关,开关与商家勾选均同开同关。
-- 现金(4)特殊:不纳入商家勾选管控(仅商家建单场景使用),平台开关也不覆盖它。
+- 现金(4)特殊:无平台开关、无就绪度(无需凭证);纳入商家勾选管控(2026-09-20 变更)。可用场景 = 商家建单(现状不变)+ 用户自取/堂食下单且商家接受(未设置默认接受);用户外送单一律拒绝。
+- 现金自取/堂食订单的到店收款为线下行为,平台不追踪现金到账,不阻塞履约(同到付语义)。
 - 到付(1)纳入管控且默认开。
 - 老客户端(不感知新字段/新逻辑):一律由后端校验拒绝并返回国际化提示;银行信息查询接口保持原结构兼容。
 
@@ -134,11 +153,11 @@
 ### Functional Requirements
 
 - **FR-001**: 系统 MUST 提供平台级支付方式开关,按"支付方式 × 维度(商家餐饮单 / 骑手闪送)"独立控制,默认全部开启。
-- **FR-002**: 某支付方式对一笔订单可用 MUST 满足三层闸门:平台开关开启 ∩ 收款方接受 ∩ 就绪度满足(线上渠道有凭证、线下转账有启用银行卡)。
+- **FR-002**: 某支付方式对一笔订单可用 MUST 满足三层闸门:平台开关开启 ∩ 收款方接受 ∩ 就绪度满足(线上渠道有凭证、线下转账有启用银行卡)。**例外**:现金(4)仅适用收款方接受层(无平台开关、无就绪度),且仅限商家建单与用户自取/堂食场景(FR-006)。
 - **FR-003**: 商家 MUST 能在商家端选择接受的支付方式,选择在主账号级生效(连锁共享);未设置或清空时 MUST 等同接受全部(存量行为零变化)。
 - **FR-004**: 系统 MUST 在创建订单时校验支付方式满足闸门,不满足时以用户所用语言返回明确错误提示(兼容老 App,不静默失败)。该校验 MUST 封装为单一独立方法,作为唯一校验入口;所有下单路径(用户餐饮下单、商家建单、闪送下单)MUST 在下单时统一调用该方法,MUST NOT 在各入口重复实现校验逻辑。
 - **FR-005**: 信用卡与 Apple Pay MUST 作为同一组方式展示与控制(同开同关)。
-- **FR-006**: 到付(1)MUST 纳入平台开关管控且默认开;现金(4)MUST 不纳入本管控(仅商家建单场景)。
+- **FR-006**: 到付(1)MUST 纳入平台开关管控且默认开;现金(4)MUST NOT 纳入平台开关与就绪度管控,MUST 纳入商家勾选(MERCHANT 维度收款方选择,未设置默认接受)。现金可用场景 = 商家建单(现状)+ 用户自取/堂食下单且商家接受;用户外送订单传现金 MUST 被拒绝并返回国际化提示。
 - **FR-007**: 用户闪送下单时 MUST 选择支付方式且被订单记录;可选项 = 闪送维度平台开关 ∩ 就绪度。
 - **FR-008**: 骑手 MUST 能设置接受的闪送收款方式;未设置时 MUST 默认接受平台开放的全部方式。
 - **FR-009**: 骑手抢单列表 MUST 只展示支付方式为其接受的闪送订单。
@@ -153,7 +172,7 @@
 
 ### Key Entities *(include if feature involves data)*
 
-- **支付方式**: 业务枚举(到付、信用卡、Apple Pay、LINE Pay、现金、线下转账),信用卡与 Apple Pay 同组。
+- **支付方式**: 业务枚举(到付、信用卡、Apple Pay、LINE Pay、现金、线下转账),信用卡与 Apple Pay 同组;现金为商家勾选组(CASH,无平台开关行)。
 - **平台支付方式开关**: 方式 × 维度(商家餐饮单/骑手闪送)× 启停状态。
 - **商家支付方式选择**: 商家主账号 → 接受的方式集合;空=全部。
 - **骑手闪送收款方式选择**: 骑手 → 接受的方式集合;空=平台开放全部。
@@ -170,6 +189,7 @@
 - **SC-004**: 027 迁移完成后,全部存量有银行信息的商家线下转账展示不回退(迁移覆盖率 100%)。
 - **SC-005**: 闪送抢单列表中不再出现骑手不接受支付方式的订单(过滤准确率 100%)。
 - **SC-006**: 平台设置页与商家端设置页全部文案覆盖四语言(vi/zh/tw/en),无硬编码。
+- **SC-007**(2026-09-20 新增): 用户外送订单直传现金 100% 被拒绝并返回所用语言提示;勾选现金的商家,其自取/堂食结算页 100% 出现现金项且订单直接进入进行中(不经待付款)。
 
 ## Assumptions
 

+ 8 - 0
specs/031-pay-method-config/tasks.md

@@ -130,6 +130,14 @@ description: "Task list for 031-pay-method-config"
 - [x] T024 quickstart.md(代码级场景已随各任务单测覆盖;DDL/迁移执行与开关手测待部署后按 quickstart 验证) 六组场景走查(SC-001~006),pymysql 只读冒烟迁移覆盖 SQL(预期 0 未覆盖)
 - [x] T025 review 子代理核对完成(2026-09-11):闸门白名单四处/缺行=开/现金放行与抢单恒可见/CARD_OMG联动/bankInfo结构与防御/确认收款幂等/DDL一致/Controller规范/存量零变化/缺省现金 全部无误:闸门调用点仅 contracts 白名单四处(grep `assertUsable`)、15 项决策与 spec 符合性、注释规范、存量零变化路径
 
+## Phase 10: 自取/堂食现金支付(US7,2026-09-20 新增,2026-09-20 起基于 test-202609v2)
+
+- [ ] T026 闸门改造:`PaymentMethodGateService` 增 `assertUsableForOrder(payType, scope, storeId, payeeUserId, orderType)` 重载与 `GROUP_CASH`/"CASH" 常量;现金判定=商家勾选(pay_methods 含 CASH 或 NULL=全部),无平台开关无就绪度;`orderType=0` 拒(复用 `pay.method.not.available`);`groupsOf(MERCHANT)` 含 CASH、`allGroups()` 不含;原 `assertUsable` 四处调用点行为不变(商家建单/闪送/bankInfo 现状放行);单测(勾选含/不含 CASH、NULL 默认、type 0 拒、type 1/2 过、原入口零变化)
+- [ ] T027 用户下单接入:`UserOrderController.createOrder` 改调 `assertUsableForOrder`(传 `item.getType()`);现金自取/堂食单 payStatus 置 1(同到付语义,不进待付款);订单列表 `active` 页签过滤放行 `payType=4 + type∈{1,2}`(`unpaid` 过滤已天然不含 4,核对即可);取消/售后路径与到付对齐核对;单测(现金自取创建、外送直传 4 拒、列表页签归属)
+- [ ] T028 结算页接口:`GET /system/storePayMethods` 增可选 `@RequestParam(required=false) Long type`;`type∈{1,2}` 且商家接受时追加 `{methodCode:"CASH", payType:"4", ready:true}`;不传/`type=0` 不追加(老客户端零变化);契约单测
+- [ ] T029 商家端 PC(foodie-store,test-202609v2):`PayMethodSettings.vue` 勾选列表加「现金」项(值域含 CASH,i18n 四语言文件 key 如 `payMethodCash`,写法遵循 `$t()` 层级规范);admin-vue 平台矩阵**不**加现金行(无平台开关);提交前跑 admin-vue `node tests/flash-delivery.test.cjs` 防误伤
+- [ ] T030 回归与核对:`mvn compile` + 闪送/订单相关定向测试不回归;grep 确认 `assertUsable` 白名单调用点更新无遗漏;quickstart 场景补充现金用例(商家勾选→自取下单选现金→进行中可见→取消行为同到付)
+
 ---
 
 ## Dependencies & Execution Order