# 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 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`(该支付方式当前不可用,请更换支付方式)。 ### 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 查询开关矩阵 ``` 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 = 平台商家维度开放集合(附就绪度标记)**追加 CASH 项(2026-09-20 变更:无平台开关、ready 恒 true,是否可用由商家勾选决定)**,selected = 商家当前选择(NULL 视为全部,返回时展开为全部可用项含 CASH)。 ### 2.2 保存选择 ``` PUT /system/merchantPayMethods Body: { methodCodes: ["COD", "CARD_OMG"] } // 空数组=清空=回落全部;值域含 "CASH"(2026-09-20 变更) ``` 写 `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),命名驼峰有意义(项目规范)。 ## 7. 用户端结算页可用支付方式(App 渲染依据) ``` GET /system/storePayMethods?storeId={storeId}&type={type} // type 可选(2026-09-20 变更):1自取/2堂食 ``` - 鉴权:匿名(`@Anonymous`,同 bankInfo 风格);App 结算页进入时调用,按返回渲染支付方式列表。 - 逻辑:`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`)。