api.md 17 KB

多店结算接口契约(实施版)

以下是 036 后端接口契约,部署前仍须执行已登记迁移并完成 App 联调;路径存在不代表已上线。路径均为 /system/multiStoreCheckout 前缀;用户接口使用 @RequestHeader String token,POST 使用明确 DTO 与 @RequestBody,GET 参数显式声明。App 由前端人员按本文接入,本仓库不修改 App 源码。错误响应使用现有 AjaxResult 及项目 i18n 消息。新用户/骑手接口必须发送 X-Multi-Store-Capability: batch-cards-v1;缺失或未知值返回 errorCode=UPGRADE_REQUIRED。普通单旧接口不要求此头。两端卡片语义见 联合方案。

用户流程

方法与路径 请求关键字段 成功响应 拒绝条件
POST /quote 地址 ID、顺序候选店与商品/规格/数量、同骑手、各店即时/预约时段、逐店备注 quoteToken、有效至、锚点/围栏、参与店及逐店原运费/折后运费/商品价/实付/可选支付方式、圈外店和不兼容原因、规则版本 店/商品/配送资格不合格、支付方式仅转账、报价配置非法;圈外店仅报告不创建
POST /batches requestKey、quoteToken、参与店 ID 列表、逐店 paymentMethods:[{storeId,payType}],可选顶层发票字段同普通下单 父批次号、子单号/金额/已选方式及状态/可选方式、两个截止时间;逐店备注已按报价快照固化到子单 remarks,可开票子单保存发票意图;含未付线上子单为 PENDING,全到付为 ACTIVE,但 ACTIVE 不等于已开放 报价过期、成员/地址/价格/规则变化、逐店方式缺漏/重复/夹带非参与店/不可用、电子发票无可开票店或资料校验失败、权限不符;同用户同 requestKey 且付款及发票载荷相同返回原批次,否则拒绝冲突
GET /batches/{batchId} token + 路径批次 ID 批次/逐店支付选择、真实实收、到付确认、退款与履约状态、截止时间、关联订单 非本人批次拒绝,不暴露其他用户父/子/组
POST /batches/members/payment body {batchId,orderId,payType,clientRequestId},payType = 1/2/3/5 新批次的安全改选:到付返回批次状态,线上返回 ONLINE_PENDING;网关创建仍走原接口 批次已生效/过操作截止、该店在途支付已实收或渠道核实未通过、商家不可用、归属不符;6 转账始终不支持。存在在途尝试时服务端先逐笔向渠道核实,确定未付(OMG UNPAID/FAILED、LINE 未捕获)即放行改选
POST /batches/cancel body {batchId,reason:"USER_CANCEL",clientRequestId} 当前入口只返回是否成功关闭未生效批次的布尔值;退款真实进度待详情接口返回 已 ACTIVE 须走子单取消规则;退款处理中不伪称退款成功
POST /batches/members/cancel body {batchId,orderId,reason:"USER_CANCEL",clientRequestId} 当前入口只返回是否成功取消该子单;退款真实进度待详情接口返回,其他成员费用快照不变 未生效不允许单店取消;骑手已接/取餐拒绝用户取消;并发接组只能一方成功

每店报价另外区分 discountedFreight(骑手毛运费)、merchantDeliverySubsidy(商家免运承担)、customerDeliveryFee(客户运费)及 amount(优惠后商品金额+客户运费)。商家满额免运以多店折后运费为基数:原运费40、八折后32、全免时客户运费0、商家补贴32、骑手仍收32。到付给商家金额为 amount - discountedFreight;普通单的免运口径不改。

回调/补单仍使用现有 LINE/OMG 入口,服务端以原订单/尝试号识别新批次成员:先验签与核实实收、落支付事实,再在事务内聚合批次;不得直接发履约新单通知。到付只表示用户确认按现有流程交餐付款,不伪称线上实收。用户列表应展示逐店已选方式、部分付款、缓冲核实、待批次生效、退款中并提供继续结算入口;普通旧单字段语义不变。

036 子单调用原 LINE/OMG 创建入口、OMG 重试入口同样须携带上述能力头,旧版 App 不能通过原网关入口绕过逐店收银。原 LINE/OMG 网关查询、异步回调、补单继续按原交易事实核实,不以客户端能力头作为可信付款事实。 LINE 创建使用 {ddId:orderNo};OMG 创建使用 {orderId:orderNo,paymentMethod:"CREDIT"} 对应 payType=2,或 paymentMethod:"APPLE_PAY" 对应 payType=5。POST /batches/members/payment 只选付款方式,不替代上述网关创建,也不返回跳转凭证。

骑手/平台

  • 骑手待抢列表对同骑手配送组只返回一个任务,显示各店及总计;已生效、到预约开放时点才可见。POST /system/multiStoreCheckout/groups/accept,JSON {groupId,version,clientRequestId},原子承接全部有效成员;旧单笔接单 API 对组成员拒绝拆抢。骑手仍逐子单取餐/送达。接组须以创建时首店为位置基准,在同一骑手锁内重验资格、位置时效、接单范围、容量与成员状态。首店到其他门店的合并围栏另行校验,不用最近店替代首店。组详情路径为 GET /groups/{groupId},待抢/已接列表和角标使用下节的统一口径。
  • 组详情 members[] 逐子单返回 legacyOrder、juli、jvli:juli 在传入骑手 longitude/latitude(成对可选,只传其一拒绝)时按定位到订单坐标的直线公里距离计算,与统一列表同口径,不传为空;jvli 为建单时服务端道路里程的公里快照,并与 legacyOrder.jvli 一致;历史无快照时为空。组卡顶层没有单一门店配送距离。
  • 商家已接未取但实际无法供餐:POST /merchant/members/cancelUnavailable,body {batchId,orderId,reason:"MERCHANT_UNAVAILABLE",clientRequestId}。仅授权门店、已接组中尚未被骑手取餐的本子单可取消;本店实收登记原路退款意图,其他店不补差;最后一个有效子单取消时结清组和父单。
  • 平台配置 GET/PUT /system/multiStoreCheckout/config:开关、围栏米、最多店数、缓冲分钟、可增删改折扣阶梯;保存会校验并更新规则版本,仅影响新报价。平台退款跟进列表显示各商家渠道、实收、退款意图/结果/待人工处理,不提供“手工标记渠道已退款”。
  • 平台退款跟进只读接口 GET /system/multiStoreCheckout/admin/refunds?page=1&size=20&status=ALL,可按 PENDING/PROCESSING/UNKNOWN/FAILED/MANUAL_REVIEW/REFUNDED 过滤;需要现有 system:order:list 权限,按退款意图逐笔分页,返回 batchId/memberId/orderId/storeId/channel/amount/merchantOrderNo/intentStatus/gatewayStatus/effectiveStatus。仅 REFUNDED 表示原渠道已证实退款;测试 OMG 无法实际退款时不得手工置为成功。
  • 平台 GET /system/order/list 仍以子单为行;当前页 036 真成员附加 multiStore={orderId,batchId,groupId,parentOrderNo,checkoutStatus,groupStatus,paymentState},普通单为 null,不改变原分页数量。平台 GET /system/multiStoreCheckout/admin/batches/{batchId} 使用 system:order:list 权限,返回只读批次和逐店进度,不暴露 App 的 allowedActions;既有 App /batches/{batchId} 仍严格检查用户归属。平台普通状态修改入口对 036 成员直接拒绝,非 036 订单沿原流程。
  • 平台配置只读写独立 order_checkout_config,不占用 sys_config。GET 返回 enabled/fenceMeters/maxStores/confirmationBufferMinutes/version/tiers,其中 tiers 为门槛数字键到系数的对象;PUT 将 tiers 作为 {minimumStores,ratio} 数组传入,并回传新版本。缺表或缺行时读取保守关闭,保存拒绝并提示先执行 036 迁移;客户端旧 version 不能覆盖新配置。
  • 新任务通知出站与预约旧时点扫描按持久记录幂等重试;组只推一个待抢任务,独立成员各推一个。用户付款通知与订单取餐/送达通知继续独立,不在未全额就绪时发骑手/商家履约新单。
  • 036 组新任务推送额外带 groupId、eventId;前端用 groupId 打开组详情,并按 eventId 去重重试推送。后端 SENT 是已提交到项目现有异步推送通道,不等于设备已送达。

时间和兼容示例

14:00 创建:建批请求已逐店选方式,到付当场确认;14:15 前可发起线上支付,未有网关尝试的待生效成员可合法改选。14:14 已实际发起的线上尝试可在 14:20 前完成付款及可信确认;14:16 不可新发起、重试或改选;14:20 仍未全部就绪则关闭,迟到实收只退款不激活。截止在创建时快照,不因配置变更或重试延长。

普通单仍调用现有 /createOrder、LINE/OMG/到付入口并保持原校验与状态;新接口不得仅凭客户端传入的批次号放宽原入口。预约订单不重定义旧时间语义;2026-10-16 已确认新多店接入 037 现有无人接单规则,按父批次统一生效时间及实际开放时点判断,不以逐子单支付时间开始。

2026-10-16 已确认的边界补充(尚待实现)

  • 批次详情返回 activatedAt,组详情返回 openedAt,并由服务端给出真实阶段/允许操作;App 不从付款时间推导无人接单倒计时。按现有阈值自动取消未承接组时,整组从待抢消失,详情保留逐店取消及退款进度,迟到实收不重开。
  • 未生效预约在激活时已到或超过结束则取消,返回原预约已过期的业务结果及真实退款进度,不返回可抢状态、不自动改即时;已生效任务不由此检查取消。
  • 建批后收货地址/坐标固定,地址簿变化不修改订单;首版无多店改址。用户要换地址按现有整批/子单取消权限处理后重新报价,不能只修改某店或绕过已接单禁取消。
  • 配置 PUT 降低最大门店数时,存在超限未结束未承接组则保存失败、原配置和版本不变;提示先处理存量组。已接组不受影响;失败不暗中取消订单或改变费用。

组合列表、角标和详情的实施目标(2026-10-19)

本节定义前端接入契约,尚未上线。新用户端调用 GET /user/cards?page=1&size=10&tab=all,同一外卖订单列表返回普通单与 036 批次卡;新骑手端调用 GET /rider/tasks?page=1&size=10&tab=newTask&longitude=...&latitude=...,返回普通外卖任务与同骑手配送组卡。骑手角标调用 GET /rider/taskCount?longitude=...&latitude=...;用户各主阶段数量如页面需要,由 GET /user/cardCount 同口径返回。两端均使用 token 确认身份,参数显式声明,不接受传入用户/骑手 ID。GET /batches/{batchId} 与 GET /groups/{groupId} 为组合详情;普通单点击仍走现有详情。退款/售后是补充筛选,可以与一个主流程页签交叉,不改变唯一主阶段。骑手列表/角标方法已归原骑手控制器,对外 URL 与响应不变;待抢组须实际开放并写入 openedAt 才可见,不能仅凭 ACTIVE 判断。

统一列表响应沿现有骑手列表习惯使用 data.records、data.total、data.current、data.size;page 从 1 起,size 限定为正数并设置服务端上限。total 是过滤后的卡片数,不是子单数。先按归属、Tab、开放/资格和组合关系得到卡片集合,再排序、分页,最后批量填充本页成员摘要;不能先分页 pos_order 再在 App 拼组。相同排序键以 cardKey 稳定打破平局;新任务有有效骑手坐标时按首店距离升序,其余按现有普通外卖 Tab 的创建时间方向。角标与列表共享同一过滤和组去重规则,刷新/并发变化以服务端最新结果为准。

字段 用户批次卡 cardType=BATCH 骑手组卡 cardType=GROUP 普通单 cardType=ORDER
身份 cardKey=BATCH:{batchId}、batchId、parentOrderNo cardKey=GROUP:{groupId}、groupId、batchId、version cardKey=ORDER:{orderId}、原订单 ID/单号
主阶段 checkoutStatus(PENDING/ACTIVE/CANCELLED)、primaryTab、refundSummary primaryTab、组承接/结束状态、原预约窗口及开放时间 沿原普通订单主阶段,不套用批次状态
进度 memberCount、readyCount、pickedUpCount、deliveredCount、cancelledCount、activeCount memberCount、activeCount、pickedUpCount、deliveredCount、cancelledCount 旧字段不变;组合计数可为空
金额 快照 totalAmount、onlinePaidAmount、codPromisedAmount、refundPendingAmount、refundedAmount,单位 TWD discountedFreightTotal(配送毛收入)、codToCollectAmount、逐店到付给商家额,单位 TWD 原字段不改
时间/距离 createdAt、operationDeadlineAt、finalDeadlineAt、activatedAt createdAt、openedAt、anchorDistanceMeters,接单按首店判断 旧字段不改
明细/操作 members[] 逐店付款与履约状态、allowedActions members[] 逐店出餐/取送状态、allowedActions,待抢时联系人脱敏 原单笔操作入口

members[] 至少包含 orderId/orderNo/storeId/storeName/payType/paymentState/orderAmount/discountedFreight/deliveryStatus/cancelStatus/refundStatus,其中到付子单另有 codToCollectAmount 和 codToMerchantAmount。只读展示字段由服务端从真实成员计算,客户端不根据某个子单 payStatus 推断批次状态、不在本地重算费用、进度或可操作性。接单前组卡不透出超出原单笔待抢范围的用户联系信息;取消成员仍在详情保留历史,但从有效进度和现金待收中剔除。activeCount 为当前有效成员数,取消后可下降;memberCount 是创建时总数,不下降。已送达成员计入已取餐数,已取消成员不计入分母;混合进度按当前有效成员统计,另外显示 cancelledCount 防误解。全组结束前不能仅因某子单售后状态释放容量。 已生效待接组的用户批次卡 allowedActions 可包含 CANCEL_MEMBER;已接组中骑手逐店摘要按真实 orderState/deliveryStatus 返回 PICKUP_MEMBER 或 DELIVER_MEMBER,最终操作入口仍重新校验状态与骑手身份。

当前只读接口为 GET /user/cards、GET /user/cardCount、GET /batches/{batchId}、GET /rider/tasks、GET /rider/taskCount、GET /groups/{groupId}。组合卡 records 为先聚合后分页;普通 ORDER 卡附带 legacyOrder 原订单快照供旧内容展示。退款进度从 036 意图关联原 LINE/OMG 退款记录:BRIDGED 只是已桥接,不代表已到账;只有渠道事实 REFUNDED 才计入 refundedAmount,未知/失败/人工处理仍计入待处理。骑手待抢组仅用服务端最近位置判断首店范围;接组再次在骑手锁内复核。

旧用户 /system/order/getorderlist 与旧骑手单笔列表/角标均不展示 036 子单,不能先对子单分页再让旧 App 拼组。旧 /system/userOrder/getParentOrderDetail 对真实 036 父单返回 UPGRADE_REQUIRED,新 App 应调用带归属校验的 GET /batches/{batchId};普通父单仍走原接口。

用户主 Tab:all/pending/active/completed/cancelled;有退款或售后记录时另支持 refund 筛选,同一卡可同时在 active 和 refund 查询中出现,但在 all 仅出现一次。骑手沿原外卖 newTask/toPickup/delivering/completed/cancelled/refund 参数:未取完的有效子单存在时始终为 toPickup,即使另一子单已送达;有效子单均已取且仍有待送/待处置时为 delivering;全部有效成员结束后进入历史。待抢组按 ACTIVE、实际开放 openedAt、原预约时点和资格过滤,且只计一张卡。业务状态、可操作性和角标由服务端返回,不以 App 页签推导权限。

用户已确认旧版 App 须升级后才能使用多店:发布前确认两端最低兼容版本和强制更新入口,配置开关在两端准备就绪前保持关闭。旧版不能发起新批次、继续逐店收银或承接组;已有批次/已接组遇旧版须引导升级,旧单笔支付/取消/接单接口仍对新批次或组成员实施服务端保护。普通单、历史单、单店转账、自取及夜市自配送继续沿旧接口和原规则;普通平台配送预约只补既有时点开放扫描,不套 036 截止。升级策略只是兼容门禁,不替代 token、订单归属、支付事实、版本和骑手锁校验。

错误至少区分 UPGRADE_REQUIRED、QUOTE_CHANGED、PAYMENT_WINDOW_CLOSED、PAYMENT_PENDING_CONFIRMATION、BATCH_CANCELLED、GROUP_CHANGED、RIDER_OUT_OF_RANGE、RIDER_CAPACITY_FULL、ORDER_ALREADY_ACCEPTED、FORBIDDEN,并以项目 i18n 消息返回;实施者可在既有错误结构中承载稳定业务码。列表过滤后的接组接口必须重新校验并返回明确原因,不能信任卡片上次可见的状态。