本文档供用户端 App 和骑手端 App 接入多店合并下单功能(036)使用,以当前后端实现为准。多店功能的全部业务规则均写在本文档内,接入不需要再查阅其他契约文件。文档只描述接口契约和业务规则,不包含 App 前端实现代码;本仓库不修改 App 源码。平台后台、商家端的 036 管理接口不在本文范围。
更新时间:2026-10-21。036 数据库迁移已在测试库执行并核对表结构;生产库尚未执行。平台多店开关仍关闭、6个相关定时任务保持暂停:接口已通过本地测试,真实支付/退款与App端到端联调尚未验证。测试库无需重复迁移;后续按联调计划部署兼容版本并开启配置和相应任务,生产库须另行执行迁移。
核心概念(先读这段):
配送距离和费用由服务端统一计算,App直接展示报价结果,不自行计算道路距离或运费;创建批次时服务端仍会重新校验。2026-10-20后端批量路线优化不改变本文请求参数、响应字段或调用顺序。
父单号和子单号沿用99开头的纯数字格式(父15位、子18位),接口字段类型仍为字符串;App必须原样展示和回传,禁止转为JavaScript Number或用前缀判断多店身份,多店身份以服务端卡片/批次字段为准。
当前支持:
当前不包含(继续走原流程,036 不改动):
payType,线上付款仍调用现有 LINE/OMG 创建接口;首次付款不需要另调 5.3 的改选接口。接口路径均为服务端相对路径,实际请求地址为 {baseUrl}{接口路径};/prod-api 等网关前缀由 App 现有请求层继续添加。
| 请求头 | 必填 | 说明 |
|---|---|---|
token |
是 | 登录 JWT。用户/骑手身份均由 token 解析,请求体与查询参数中不提交 userId/riderId |
X-Multi-Store-Capability |
是 | 固定值 batch-cards-v1。本文全部 /system/multiStoreCheckout 接口,以及 036 子单调用原 POST /pay/line/create、POST /pay/omg/create、POST /pay/omg/retry 时都必须携带 |
Content-Type |
POST 必填 | application/json |
能力头规则:
batch-cards-v1 时,返回 code=500 且带 errorCode=UPGRADE_REQUIRED,msg 为"请更新应用后使用多店结算和配送组功能"。App 收到后应引导升级。{ "code": 200, "msg": "操作成功", "data": {} }
code=200 判断业务成功,不能只看 HTTP 状态码;msg 已由后端国际化,可直接展示。UPGRADE_REQUIRED 外,其他业务失败没有稳定的 errorCode:展示 msg 并刷新详情,不要按 errorCode 写分支逻辑。列表接口统一返回:
{ "records": [], "total": 100, "current": 1, "size": 10 }
page 从 1 起;size 为 1–50 的正整数,超过 50 报错。total 是卡片数(一个批次/一个组各算 1 张),不是子单数。2026-10-21T18:00:00),直接展示,不在 App 重算付款截止或倒计时。报价(quote) → 建批并提交逐店 payType(配送组创建为 WAITING)→ 线上子单逐店支付(如有)→ 全部就绪 ⇒ ACTIVE
└→ 超时/整批取消/预约过期 ⇒ CANCELLED
开放:ACTIVE 且即时/已到预约开放时点 → openedAt 有值、子单可见 → 骑手可抢
配送组:WAITING(建批时即存在,openedAt 有值后可抢)→ ACCEPTED(已接,逐店取送)→ FINISHED / CANCELLED
checkoutStatus| 值 | 含义 |
|---|---|
PENDING |
未生效:处于逐店收银与缓冲核实期,订单不可履约、不可抢 |
ACTIVE |
全部子单合法就绪;配送组在建批时已创建,须等 openedAt 有值才进入骑手待抢列表 |
CANCELLED |
已关闭:未全部就绪超时、用户整批取消或预约过期关闭 |
PENDING 可能已有部分子单实际付款,须逐店看 paymentState。ACTIVE 只表示批次就绪,不保证已经开放或推送:即时单/已到开放时点的预约单在事务提交后立即尝试开放;未到时点的预约单等待任务扫描。骑手端以组卡的 openedAt 和任务列表为准,不能仅凭 checkoutStatus=ACTIVE 显示可抢。
paymentState| 值 | 含义 |
|---|---|
UNSELECTED |
旧批次尚未选择支付方式;新建批次不会产生此状态 |
ONLINE_PENDING |
已选线上方式(LINE/OMG),等待用户发起并完成支付 |
ONLINE_PAID |
服务端收到渠道可信回调,已实际收款 |
COD_CONFIRMED |
用户已确认到付,交餐时向骑手付款 |
ONLINE_PENDING 仅表示"已选方式",不是实收;支付回跳后必须刷新批次详情,以服务端状态为准。
payType| 值 | 含义 | 说明 |
|---|---|---|
"1" |
到付 | 建批时即确认,无需网关 |
"2" |
OMG 信用卡 | 建批返回 ONLINE_PENDING 后调 /pay/omg/create |
"3" |
LINE Pay | 建批返回 ONLINE_PENDING 后调 /pay/line/create |
"5" |
OMG Apple Pay | 建批返回 ONLINE_PENDING 后调 /pay/omg/create |
"6" |
线下转账 | 新批次不支持,提交即被拒 |
availablePayTypes 由服务端按门店返回(可能不含某些方式,如商家不可用到付),App 只按返回渲染。
cardType / cardKey| cardType | cardKey | 出现在 |
|---|---|---|
ORDER |
ORDER:{orderId} |
用户订单列表、骑手任务列表(普通单,字段在 legacyOrder,点击走原普通单详情) |
BATCH |
BATCH:{batchId} |
用户订单列表(多店批次) |
GROUP |
GROUP:{groupId} |
骑手任务列表(同骑手配送组,一组一卡) |
cardKey 是列表唯一键,分页排序平局时也以它稳定打破。primaryTab 是服务端判定的主页签,App 不自行推导。
tab| 端 | 取值 | 说明 |
|---|---|---|
| 用户端 | all / pending / active / completed / cancelled / refund |
refund 是补充筛选:同一张卡可同时出现在 active 和 refund,但在 all 只出现一次 |
| 骑手端 | newTask / toPickup / delivering / completed / cancelled / refund |
只要有有效子单未取完,组始终在 toPickup(即使另一子单已送达);有效子单全部取完且仍有待送才进 delivering |
不要先对子单分页再在 App 拼卡;服务端已先聚合后分页。
allowedActions(仅用于展示按钮)| 动作 | 出现在 | 含义 |
|---|---|---|
SELECT_PAYMENT |
BATCH 卡 | PENDING 且未过 operationDeadlineAt,可继续逐店收银 |
CANCEL_BATCH |
BATCH 卡 | PENDING,可尝试整批取消 |
CANCEL_MEMBER |
BATCH 卡 | ACTIVE 且配送组待接,可尝试单店取消 |
ACCEPT_GROUP |
GROUP 卡 | 组待抢,可整组接单 |
PICKUP_MEMBER / DELIVER_MEMBER |
GROUP 卡 members[] | 本人已接组后,该店当前可取餐/可送达的提示 |
按钮提交时服务端会重新校验(骑手抢单、状态变化都可能拒绝),失败后刷新详情,不在本地重算余店运费。
逐店金额字段(报价 stores[] 与卡片 members[] 同名同义):
| 字段 | 说明 |
|---|---|
originalFoodAmount / foodAmount |
商品原价合计 / 优惠后商品金额 |
originalFreight / discountedFreight |
原运费 / 多店折扣后运费(骑手毛运费) |
customerDeliveryFee |
客户支付的运费 |
merchantDeliverySubsidy |
商家满额免运承担的补贴 |
amount |
用户应付 = foodAmount + customerDeliveryFee |
codToMerchantAmount |
到付子单给商家的金额 = orderAmount - discountedFreight |
商家满额免运以多店折后运费为基数。例:原运费 40、八折后 32、全免时客户运费 0、商家补贴 32、骑手仍收 32。普通单的免运口径不变。
报价是短期快照,服务端建批时会重算并比对;App 不提交自算金额。
POST /system/multiStoreCheckout/quote。availablePayTypes 与 excludedStores[];用户为每家参与店选择付款方式,按 storeId 组成 paymentMethods。发票选择沿普通订单整批只选一份,服务端只给可开票参与店子单保存意图;然后一次提交 POST /system/multiStoreCheckout/batches,保存 batchId 与各店 orderId/orderNo。圈外店不创建订单,引导剔除或另行单店下单。COD_CONFIRMED,无需再次确认;线上子单为 ONLINE_PENDING,用该店 orderNo 调原渠道创建接口发起付款。仅在用户随后改选方式时调用 POST /system/multiStoreCheckout/batches/members/payment(见 5.3)。GET /system/multiStoreCheckout/batches/{batchId},以服务端状态为准。GET /user/cards + GET /user/cardCount;不要调旧列表接口看多店单(旧接口不返回 036 子单/批次)。POST /system/position/setRiderposition):待抢组资格用服务端最近位置与首店距离判断。GET /rider/tasks、角标 GET /rider/taskCount;组卡一张 = 一组。GET /groups/{groupId} → 确认接单 POST /groups/accept(带 version),成功后刷新组详情。members[].orderNo 调原单笔接口逐店取餐/送达(见 6.5);不要按成员逐单抢、也没有"整组送达"按钮。以下接口均需 token + X-Multi-Store-Capability: batch-cards-v1。
POST /system/multiStoreCheckout/quote
请求体示例(ID 须替换为真实值):
{
"addressId": 123,
"sameRider": true,
"stores": [
{ "storeId": 101, "deliveryWindow": null,
"items": [{ "productId": 1001, "quantity": 1, "specValueIds": [] }],
"couponId": null, "forcePath": null, "remarks": null },
{ "storeId": 102, "deliveryWindow": null,
"items": [{ "productId": 2001, "quantity": 2, "specValueIds": [] }],
"couponId": null, "forcePath": null, "remarks": null }
]
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
addressId |
long | 是 | 收货地址 ID,须属于当前用户 |
sameRider |
boolean | 是 | 当前版本必须传 true(仅支持同骑手合并配送),传 false 报错 |
stores |
array | 是 | 按用户选择顺序排列,至少 2 家;首店是合并围栏与骑手接单距离的固定锚点 |
stores[].deliveryWindow |
string | 否 | null=即时配送;预约传 "yyyy-MM-dd HH:mm-HH:mm"(22 字符,如 2026-10-21 18:00-18:30)。所有店必须完全相同,不得混合即时与预约 |
stores[].items[].productId |
long | 是 | 商品 ID;只提交商品/规格 ID,客户端价格不作为计价依据 |
stores[].items[].quantity |
int | 是 | 数量 |
stores[].items[].specValueIds |
long[] | 是 | 规格 ID 列表,无规格传 [] |
stores[].couponId |
long | 否 | 门店优惠券 ID,无传 null |
stores[].forcePath |
string | 否 | 预留字段,正常流程传 null |
stores[].remarks |
string | 否 | 逐店订单备注,随报价快照固化到对应子单 remarks 字段(口径同普通单逐店备注),不参与计价;无备注传 null |
建批后的子单 food 商品快照由服务端按 specValueIds 查数据库生成:有规格时同时返回 ask(规格名称数组,兼容原订单详情展示)和 specValueIds(规格值 ID 数组)。免费规格的 otherPrice 可以为 0,但 ask 仍应展示;不要只传客户端的 ask 文本代替规格 ID。此修复只影响新创建的版本 2 快照,不回写历史订单。
响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
quoteToken |
string | 报价令牌,建批时提交 |
expiresAtEpochMillis |
long | 报价过期时间(毫秒时间戳) |
ruleVersion |
long | 规则版本;平台改折扣配置后旧报价不可用 |
anchorStoreId |
long | 锚点门店 ID(首店) |
fenceMeters |
int | 合并围栏半径(米),首店到其他店的距离上限 |
participatingStoreIds |
long[] | 可参与的门店 ID 列表 |
stores[] |
array | 逐店报价,字段见 3.8,另有 availablePayTypes |
stores[].routeDistanceMeters |
number | 服务端矩阵计算的该店到收货地址道路米数;建批时重新报价,并固化为子单 jvli(公里) |
excludedStores[] |
array | 圈外/不兼容门店:{storeId, reason}。不自动创建订单,须剔除或另行结算 |
freightRatio |
number | 当前折扣系数 |
fingerprint |
string | 报价指纹(服务端内部使用,App 原样忽略即可) |
规则:
excludedStores 并给 reason;被排除到参与店不足 2 家时报价报错,错误 msg 会点名被排除门店与原因(如"以下门店无法参与多店配送:快食到家(超出与首店的配送范围)。参与门店不足2家,请调整门店后重试")。POST /system/multiStoreCheckout/batches
请求体:
{
"requestKey": "本次提交固定唯一键",
"quoteToken": "报价返回值",
"participatingStoreIds": [101, 102],
"paymentMethods": [
{ "storeId": 101, "payType": "3" },
{ "storeId": 102, "payType": "1" }
],
"invoiceChoice": "COMPANY",
"buyerUbn": "12345678",
"buyerName": "甲公司",
"buyerEmail": "invoice@example.com",
"saveAsDefault": false
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
requestKey |
string | 是 | 幂等键:网络重试须沿用同一键、同一报价与逐店支付选择,才返回原批次;同键改动载荷会被拒,换新键可能创建新批次 |
quoteToken |
string | 是 | 5.1 返回的报价令牌;过期报"报价已过期,请重新获取" |
participatingStoreIds |
long[] | 是 | 用户明确确认参与的门店,须是报价参与店的子集且 ≥2 家;夹带圈外店会被拒 |
paymentMethods |
array | 是 | 与参与店一一对应的 {storeId,payType};每店恰好一条,payType 是字符串且须在该店报价的 availablePayTypes 内;不支持转账 "6" |
invoiceChoice |
string | 否 | 与普通订单相同:PAPER、PHONE_BARCODE、CITIZEN、MEMBER、LOVE_CODE、COMPANY;不传则不保存发票意图。整批只选一次,不是逐店数组 |
carrierType / carrierNum |
string | 按选择 | 手机条码/自然人凭证使用普通订单同名字段与校验;服务端按选择规范化载具类型 |
buyerUbn / buyerName / buyerEmail |
string | COMPANY 时 |
公司统编、名称及邮箱,沿普通订单校验;每个可开票子单各自开票 |
loveCode |
string | LOVE_CODE 时 |
捐赠码,沿普通订单联网校验 |
saveAsDefault |
boolean | 否 | 与普通订单相同;仅支持保存载具/公司抬头,纸本、会员及捐赠不保存 |
不要只传普通单店请求的顶层 paymentMethod 或顶层 payType:多店建批只读取逐店的 paymentMethods,缺少、重复、夹带非参与店或选了该店不可用的方式都会被拒。电子发票至少需要一个参与店可开票;混合门店时不可开票的子单不生成发票意图,纸本选择按普通订单处理。建批只是保存意图,出餐后由各子单原开票流程处理,不代表提交时已经开票。网络重试须保持同一个 requestKey、quoteToken、参与店、逐店方式和发票字段;创建成功后要改方式,调用 5.3,不要用同一个 requestKey 重发不同的付款或发票资料。
响应 data:
| 字段 | 类型 | 说明 |
|---|---|---|
batchId |
long | 批次 ID,后续接口都用它 |
parentOrderNo |
string | 父批次号 |
checkoutStatus |
string | 含未付款线上子单时为 PENDING;全到付时创建即为 ACTIVE。ACTIVE 不等于骑手已可抢,实际开放看组 openedAt 与骑手列表 |
totalAmount |
int | 批次总金额(TWD 元) |
operationDeadlineAt |
string | 操作截止(创建后 15 分钟,见 8.1) |
finalDeadlineAt |
string | 最终截止(创建后 20 分钟,见 8.1) |
members[] |
array | {orderId, orderNo, storeId, amount, payType, paymentState, availablePayTypes[]},逐店保存好 orderNo |
注意:幂等重试返回的 availablePayTypes 可能为空数组,不能当作门店永久不支持付款;以最新报价和付款接口校验为准。
建批响应中的各店 payType / paymentState 是本次已保存的选择。全到付批次会在建批事务提交后立即尝试开放即时单;若是未到开放时点的预约单,仍保持 ACTIVE、等待开放,App 不要因 ACTIVE 提前展示可抢或已推送。
以上示例中,101 店返回 payType="3"、paymentState="ONLINE_PENDING",须继续发起 LINE Pay;102 店返回 payType="1"、paymentState="COD_CONFIRMED",不再调 5.3 确认到付。只有两店均选到付时,建批结果才可直接为 ACTIVE;此时不能再改选支付方式。
POST /system/multiStoreCheckout/batches/members/payment
请求体:
{ "batchId": 7, "orderId": 701, "payType": "3", "clientRequestId": "本次操作唯一键" }
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
batchId / orderId |
long | 是 | 批次与该店子单 |
payType |
string | 是 | "1" 到付 / "2" "3" "5" 线上(见 3.4);"6" 转账不支持 |
clientRequestId |
string | 是 | 本次操作唯一键,非空且 ≤64 字符 |
响应 data(字符串):
| 场景 | 返回 |
|---|---|
payType="1" 改选到付 |
该店改选后的批次状态:"PENDING"(还有店没就绪)/ "ACTIVE"(全部就绪已生效)/ "CANCELLED"(预约已过期被关闭) |
payType="2"/"3"/"5" 线上选择 |
固定 "ONLINE_PENDING",随后调 5.4 发起真实付款 |
规则:
PENDING 且未过 operationDeadlineAt,ACTIVE 后不可再改;改选成功后刷新 5.5 批次详情。availablePayTypes 内;不可用会被拒。拿到 ONLINE_PENDING 后,用该店 orderNo 调原创建接口:
| payType | 接口 | 请求体 |
|---|---|---|
"3" LINE Pay |
POST /pay/line/create |
{ "ddId": "该店 orderNo" } |
"2" OMG 信用卡 |
POST /pay/omg/create |
{ "orderId": "该店 orderNo", "paymentMethod": "CREDIT" } |
"5" OMG Apple Pay |
POST /pay/omg/create |
{ "orderId": "该店 orderNo", "paymentMethod": "APPLE_PAY" } |
POST /pay/omg/retry)同样必须带能力头。GET /system/multiStoreCheckout/batches/{batchId}
路径参数 batchId。响应 data 为批次卡对象(字段见 7.1/7.2):逐店支付选择、真实实收、到付确认、退款与履约状态、两个截止时间、activatedAt、allowedActions。非本人批次拒绝,不暴露其他用户的父/子/组。
GET /system/multiStoreCheckout/user/cards?page=1&size=10&tab=all
| 参数 | 必填 | 说明 |
|---|---|---|
page |
否 | 默认 1 |
size |
否 | 默认 10,上限 50 |
tab |
否 | 默认 all,取值见 3.6 |
响应 data 为分页结构(见 2.3),records[] 为卡片对象(见 7.1):
ORDER 卡:原订单快照在 legacyOrder,展示与点击继续走原普通单详情。BATCH 卡:通过 batchId 打开 5.5 批次详情。GET /system/multiStoreCheckout/user/cardCount
无查询参数。响应 data:
{ "all": 12, "pending": 1, "active": 2, "completed": 8, "cancelled": 3, "refund": 1 }
POST /system/multiStoreCheckout/batches/cancel
{ "batchId": 7, "reason": "USER_CANCEL", "clientRequestId": "本次操作唯一键" }
reason 固定传 "USER_CANCEL";clientRequestId 非空且 ≤64 字符。PENDING 可整批取消;已 ACTIVE 须走 5.9 单店取消。data=true 只表示取消已受理,不表示渠道退款到账;退款进度看 5.5 详情。POST /system/multiStoreCheckout/batches/members/cancel
{ "batchId": 7, "orderId": 701, "reason": "USER_CANCEL", "clientRequestId": "本次操作唯一键" }
ACTIVE 且配送组仍待接(WAITING)时允许;骑手已接组后用户不能主动取消。data=true 只表示取消已受理;其他成员费用快照不变、不追补余店运费;退款进度看详情。以下接口均需骑手 token + X-Multi-Store-Capability: batch-cards-v1。定位上报继续用现有 POST /system/position/setRiderposition。
GET /system/multiStoreCheckout/rider/tasks?page=1&size=10&tab=newTask&longitude=121.5&latitude=25.03
| 参数 | 必填 | 说明 |
|---|---|---|
page / size |
否 | 默认 1/10,size 上限 50 |
tab |
否 | 默认 newTask,取值见 3.6 |
longitude / latitude |
否 | 骑手坐标:计算 juli 距离(列表与组详情同口径)并用于普通单排序。不能替代服务端位置:待抢组资格仍以服务端最近上报位置与首店距离判定,二者须同时传或同时不传 |
响应 data 为分页结构,records[] 中 GROUP 卡一张 = 一组(角标与列表同口径,组只计一次)。
普通单卡与组卡 members[] 均附带与旧骑手列表逐单一致的信息:legacyOrder(订单全量快照)、
store(门店对象)、user(用户对象)、goodsCount(food 快照件数合计,口径同统一列表:
普通单卡为本单件数,批次/组卡为有效成员合计,food 缺失或非法为 null)。
距离字段统一口径(列表、组详情一致):
| 字段 | 出现位置 | 含义 | 计算前提 |
|---|---|---|---|
juli |
普通单卡、组卡 members[] |
骑手请求定位 → 订单坐标的直线距离(公里,两位小数) | 请求带 longitude/latitude,不传为 null |
anchorDistanceMeters |
GROUP 卡 | 服务端最近骑手位置 → 首店取餐坐标(米),待抢排序也用它 | 服务端定位上报;待抢资格要求 5 分钟内新鲜 |
jvli |
组卡 members[] |
门店 → 收货地址的配送路线距离(公里,订单快照) | 无需参数,取自 legacyOrder.jvli,可能为空 |
待抢组只在批次已生效且达到原预约开放时点、组 openedAt 已写入后可见;新任务有有效坐标时按首店距离升序,其余按创建时间方向。最后成员就绪并提交后,服务端立即尝试开放即时/已到点批次;若失败,由原分钟任务重试,预约未到也由该任务到点开放。App 不依赖固定一分钟延迟,付款后可刷新列表,未出现时以服务端状态为准;对外 URL 保持不变。
GET /system/multiStoreCheckout/rider/taskCount?longitude=121.5&latitude=25.03
参数同 6.1:longitude/latitude 可选、成对传入;待抢(newTask)计数按服务端最近位置与距离上限字典判定,不依赖请求坐标。
响应 data:
{ "newTask": 3, "toPickup": 1, "delivering": 2, "completed": 9, "cancelled": 1, "refund": 0 }
GET /system/multiStoreCheckout/groups/{groupId}?longitude=121.5&latitude=25.03
路径参数 groupId;可选 longitude/latitude(骑手定位,传入后 members[].juli 按定位计算,不传为 null)。响应 data 为组卡对象(见 7.1/7.2),关键字段:
groupStatus:WAITING 待抢 / ACCEPTED 已接 / FINISHED 已结束 / CANCELLED 已取消。version:整组接单的乐观锁版本。openedAt:组实际开放时点;anchorDistanceMeters:距首店距离。allowedActions:待抢时 ACCEPT_GROUP;本人已接组后 members[] 内出现 PICKUP_MEMBER / DELIVER_MEMBER。codToCollectAmount:有效到付子单的待收总额。成员摘要(含待抢阶段)附带 legacyOrder/store/user/juli/jvli/goodsCount,
其中 juli 为骑手定位到该店订单坐标的直线距离(公里,两位小数,口径同 6.1 距离字段表):传入 longitude/latitude 时计算,不传或订单无坐标时为 null;jvli 是该店到收货地址的道路距离(公里),与 legacyOrder.jvli 一致。组内每店距离不同,组卡顶层不提供单一 jvli。历史子单若建单时未保存距离则保持 null,不把直线距离伪装成原道路距离。
其他订单字段与旧骑手列表逐单口径一致,
可直接复用旧列表项渲染;已接单后逐店履约仍按子单详情读取(见 6.5)。如产品后续要求待抢阶段
脱敏联系人与地址,须另行变更口径并同步两端。
POST /system/multiStoreCheckout/groups/accept
{ "groupId": 9, "version": 0, "clientRequestId": "本次接单唯一键" }
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
groupId |
long | 是 | 组 ID |
version |
long | 是 | 列表/详情返回的版本号;不符返回"配送组状态已变化,请刷新后重试" |
clientRequestId |
string | 是 | 本次接单唯一键,非空且 ≤64 字符 |
data 返回组 ID;服务端在同一骑手锁内复核资格、位置时效、首店距离、容量与成员状态,并发接组只能一方成功。接组后按 members[].orderNo 调原单笔接口:
GET /system/orderQsOprate/getOrderDetial?ddId={该店 orderNo} -- 取已归属骑手的完整订单/地址
POST /system/orderQsOprate/pickupOrder -- body: { "id": 701, "qsImg": "现有图片值" }
POST /system/orderQsOprate/deliverOrder -- body: { "id": 701, "qsImg": "现有图片值" }
id 是该店子单的 orderId;qsImg 语义与普通单一致。toPickup;全部取完才进 delivering(见 3.6)。| 字段 | 位置 | 含义 |
|---|---|---|
codToCollectAmount |
组卡 | 有效到付子单向用户待收的现金总额 |
codToCollectAmount |
members[] | 该店向用户收的金额 |
codToMerchantAmount |
members[] | 交给商家的金额 = orderAmount - discountedFreight |
线上已付子单不计入现金待收;已取消成员从现金待收与有效进度中剔除(详情保留历史行)。
| 字段 | 类型 | 适用 | 说明 |
|---|---|---|---|
cardType |
string | 全部 | ORDER / BATCH / GROUP |
cardKey |
string | 全部 | 唯一键:ORDER:{orderId} / BATCH:{batchId} / GROUP:{groupId} |
orderId |
long | ORDER | 普通单订单 ID |
legacyOrder |
object | ORDER | 原订单完整快照(PosOrder 原字段),旧内容展示用 |
batchId |
long | BATCH/GROUP | 批次 ID |
groupId / version |
long | GROUP | 组 ID 与接单乐观锁版本 |
parentOrderNo |
string | BATCH | 父批次号 |
orderNo / storeId / storeName / payType / orderState / deliveryStatus |
— | ORDER | 沿用普通单字段语义 |
checkoutStatus |
string | BATCH | 见 3.2 |
groupStatus |
string | GROUP | WAITING / ACCEPTED / FINISHED / CANCELLED |
primaryTab |
string | 全部 | 服务端判定的主页签 |
refundSummary |
string | BATCH | 退款汇总:REFUNDED 已有渠道证实到账;PENDING 等表示仍在处理;null 无退款 |
memberCount / activeCount |
int | BATCH/GROUP | 创建时成员总数(不下降)/ 当前有效成员数(取消后下降) |
readyCount |
int | BATCH | 已就绪成员数(ONLINE_PAID + COD_CONFIRMED) |
pickedUpCount / deliveredCount / cancelledCount |
int | GROUP | 未取消且已取(deliveryStatus≥2)/ 已送达(=3)/ 已取消 |
totalAmount |
int | BATCH | 批次总金额快照 |
onlinePaidAmount / codPromisedAmount |
int | BATCH | 线上实收合计 / 到付承诺合计 |
refundPendingAmount / refundedAmount |
int | BATCH | 退款处理中 / 已到账金额 |
discountedFreightTotal |
int | GROUP | 有效成员折后运费合计(骑手配送毛收入) |
codToCollectAmount |
int | GROUP | 见 6.6 |
createdAt |
string | 全部 | 创建时间 |
operationDeadlineAt / finalDeadlineAt |
string | BATCH | 两个截止时间,见 8.1 |
activatedAt |
string | BATCH | 批次实际生效时间 |
openedAt |
string | GROUP | 组实际开放(进入待抢)时间 |
anchorDistanceMeters |
number | GROUP | 距首店距离(米) |
members[] |
array | BATCH/GROUP | 逐店摘要,见 7.2 |
allowedActions |
string[] | 全部 | 见 3.7 |
members[]| 字段 | 类型 | 说明 |
|---|---|---|
orderId / orderNo |
long/string | 子单 ID / 单号 |
storeId / storeName |
long/string | 门店 |
payType |
string | 该店已选支付方式;新建批次必有值,旧批次未选时可能为 null |
paymentState |
string | 见 3.3 |
orderAmount |
int | 该店订单金额 |
discountedFreight |
int | 该店折后运费 |
deliveryStatus |
long | 沿用普通单:1 已接单待取餐 / 2 已取餐 / 3 已送达 |
orderState |
long | 沿用普通单订单状态 |
cancelStatus |
string | ACTIVE 有效 / CANCELLED 已取消 |
refundStatus |
string | NONE 无退款记录;其他为退款处理状态,仅渠道证实的 REFUNDED 表示已到账 |
codToCollectAmount / codToMerchantAmount |
int | 仅到付已确认且未取消的成员返回,见 6.6 |
allowedActions |
string[] | 仅本人已接组时出现 PICKUP_MEMBER / DELIVER_MEMBER |
客户端不得根据某个子单 payStatus/paymentState 推断批次状态,也不在本地重算费用、进度或可操作性。
批次创建时快照两个截止时间(默认创建 +15 分钟 / +20 分钟,以服务端返回为准),不因配置变更或重试延长:
CANCELLED),迟到实收只退款不激活。ACTIVE(线上须 ONLINE_PAID、到付须 COD_CONFIRMED);部分就绪不开放履约。ACTIVE;仍需按 5.2 的开放规则判断何时可抢。ACTIVE 后不可用 5.3 改方式。| 批次状态 | 用户可做的取消 | 接口 |
|---|---|---|
PENDING |
整批取消 | POST /batches/cancel |
ACTIVE 且组 WAITING |
单店取消 | POST /batches/members/cancel |
ACTIVE 且组 ACCEPTED |
不能主动取消 | — |
REFUNDED 才算到账(计入 refundedAmount);BRIDGED 等中间状态仅表示已登记,仍在 refundPendingAmount 处理中。activatedAt(批次生效)/openedAt(组开放)由服务端返回,App 不从付款时间推导无人接单倒计时。建批后收货地址/坐标固定,地址簿变化不影响订单;首版无多店改址。用户换地址须按 8.2 取消后重新报价下单。
/system/order/getorderlist、旧骑手单笔列表/角标均不返回 036 子单/批次/组;旧父单详情 /system/userOrder/getParentOrderDetail 对真实 036 父单返回 UPGRADE_REQUIRED,新 App 一律调 5.5。ACTIVE 时登记持久事件,组真正开放后事件转为 OPENED,独立通知任务随后处理;不保证支付回调返回时设备已收到推送。预约未到开放时点、部分店未就绪时不推送。骑手端以 6.1 列表/6.2 角标判断可抢任务,不以推送到达时间判断。groupId、eventId:用 groupId 打开组详情,按 eventId 对重试推送去重。SENT 只表示已提交到现有异步通道,不等于设备已送达。msg 文案随 App 语言本地化,下表按简体中文文案识别(按语义匹配即可):
msg |
常见原因 | App 处理建议 |
|---|---|---|
请更新应用后使用多店结算和配送组功能(errorCode=UPGRADE_REQUIRED) |
能力头缺失/错误,或旧版 App 调了旧父单详情 | 引导升级;新接口补齐 X-Multi-Store-Capability: batch-cards-v1 |
| 多店结算暂未开放 | 平台开关未开或 036 迁移未执行 | 提示暂不可用,隐藏多店入口 |
| 多店结算请求无效或参与店铺不足 | 参数缺失/少于 2 店/状态不允许/幂等键冲突 | 检查参数后重试;仍失败刷新详情 |
| 该店已发起OMG/LINE Pay支付请求,完成前不能更换支付方式;请返回完成支付,或取消本次多店结算重新下单 | 在途收银台会话经渠道核实未通过或结果未知(换方式前服务端逐笔查询渠道状态) | 引导返回原收银台完成支付;或走 5.8/5.9 取消批次后重新下单 |
| 订单已支付,无法更换支付方式 | 该店在途支付经渠道核实已收款 | 引导刷新支付结果,进批次详情查看就绪进度 |
| 收货地址不存在或不属于当前用户 | addressId 无效或非本人 | 让用户重新选择地址 |
| 门店不可参与当前多店配送 | 店在围栏外或不支持合并配送 | 按 excludedStores 剔除该店 |
| 商品、规格或优惠信息已变化,请重新结算 | 建批时服务端重算与报价不一致 | 回到购物车重新报价 |
| 当前配送费配置不可用,请稍后重试 | 运费配置缺失 | 稍后重试报价 |
| 报价已过期,请重新获取 | quoteToken 超时 | 重新调 5.1 |
| 以下门店无法参与多店配送:…(超出与首店的配送范围/暂无可用的支付方式) | 参与店被围栏或支付方式筛到不足 2 家 | 按点名门店调整选择或换店 |
| 请通过多店批次流程取消订单 | 对 036 子单调了旧取消入口 | 改用 5.8/5.9 |
| 多店配送组必须整组接单 | 对组成员调了旧单笔接单 API | 改用 6.4 |
| 配送组状态已变化,请刷新后重试 | 接单 version 过期或已被他人接走 | 刷新列表/详情 |
| 骑手位置已过期,请更新定位 | 接单时服务端位置过旧 | 先上报定位再重试 |
| 距离首店过远,无法接单 | 骑手当前位置距首店超限 | 移动后再试或放弃该组 |
| 接单距离配置不可用,请联系平台 | 平台距离字典缺失 | 提示联系平台,等待配置恢复 |
ACTIVE;有线上未付时不开放履约,全部就绪才生效;付款回跳后刷新真实状态而非本地置为已付。storeId、夹带非参与店、传 "6" 或该店不可用方式均被拒;用相同 requestKey 改传不同方式不能覆盖原批次,创建后改选走 5.3。ACTIVE 也不可抢、不推送;预约过期不激活、只退实收;生效与组开放分别以 activatedAt/openedAt 展示。codToCollectAmount、codToMerchantAmount 按 6.6 展示,线上单不计现金待收。total 为卡片数;cardKey 作唯一键;旧列表接口确认看不到 036 单。msg 展示并刷新详情,除 UPGRADE_REQUIRED 外不依赖 errorCode。