# 闪送 v2 多点取送前端对接说明 更新时间:2026-10-14。本文对应当前仓库中的后端实现,供用户端和骑手端接入;联调前需确认服务端部署版本和 [10 月 14 日数据库执行说明](../updatesql/sql.md)。 ## 本次要调整的页面和流程 | 页面/场景 | 前端调整 | | --- | --- | | 用户填写闪送订单 | 按顺序编辑一个 `stops` 数组:先全部取货站,再全部收货站;物品信息只填写一次。根据 `/home` 的取货点和收货点数量上限控制可添加数量。 | | 报价与下单 | 使用 `/v2/quote` 和 `/v2/orders`。创建时把本次报价的配置 ID、版本和费用原样回传;创建请求号在重试时保持一致。 | | 用户/收件人订单列表和详情 | 展示取货、收货两个进度;站点使用 `stops[].id`,不可把数组下标当成站点 ID。逐站收货码按权限展示。 | | 骑手履约 | 从详情的 `currentTargetStopId` 找当前目标;每站上传凭证并刷新详情。逐站送达改用固定路径的新接口,`orderId`、`stopId`、PIN 和照片全部放 body。 | | 待接单改址 | v2 使用新的完整站点报价与保存接口。保存成功后刷新详情,替换本地旧站点 ID 和收货码。 | | 发起人消息 | 识别两种逐站进度事件,凭 `ddId` 打开订单详情,再读取最新状态。逐站收件人通知本期不接入。 | 所有下列新 POST 接口使用 `token` 请求头和 JSON body。只有**新接口**采用“业务参数统一放 body”的约定;现有 GET、旧取件、旧目标调整、旧订单接口及 LINE Pay 接口的参数格式保持原样。响应仍为项目通用 `AjaxResult`,业务数据在 `data` 中;订单详情的 `data` 直接是订单对象,不再套一层 `order`。 ## 用户端:填写、报价、创建 先调用 `GET /system/flashDelivery/home`,使用 `data.pickupStopLimit`、`data.deliveryStopLimit` 控制添加站点按钮和提交数量。有效范围为 1–10,默认均为 1;对应上限大于 1 即可使用多站点,无需开关。兼容字段 `showMultiPickup`、`showMultiDropoff` 分别表示对应上限是否大于 1,`showUrgentOption` 始终为 true;加急选项直接展示。服务类型、配送等级和可用支付方式也从首页响应读取。 `POST /system/flashDelivery/v2/quote` 的示例 body: ```json { "serviceType": "HELP_SEND", "deliveryType": "NORMAL", "vehicleType": 1, "packageType": "DOCUMENT", "quantity": 2, "weightRange": "UP_TO_5_KG", "specification": "两个文件袋", "tipAmount": 0, "deliveryMode": "NOW", "payType": "4", "stops": [ {"type":"PICKUP","name":"取件人甲","phone":"0911000001","address":"台北市信义区甲路1号","addressDetail":"1楼","longitude":121.5601,"latitude":25.0301,"remark":"取文件袋甲"}, {"type":"PICKUP","name":"取件人乙","phone":"0911000002","address":"台北市信义区乙路2号","longitude":121.5602,"latitude":25.0302,"remark":"取文件袋乙"}, {"type":"DELIVERY","name":"收件人丙","phone":"0911000003","address":"台北市大安区丙路3号","longitude":121.5603,"latitude":25.0303,"remark":"交给本人"}, {"type":"DELIVERY","name":"收件人丁","phone":"0911000004","address":"台北市大安区丁路4号","longitude":121.5604,"latitude":25.0304,"remark":"交前台"} ] } ``` 至少一个 `PICKUP` 和一个 `DELIVERY`,取货站必须排在所有收货站之前,路线顺序就是数组顺序;同一地址或坐标不能重复。每站分别填写姓名、电话、完整地址和经纬度,可选门牌、交接方式、最长 200 字备注。v2 站点不要提交旧结构中的逐站物品字段;`packageType`、正整数 `quantity`、可选 `weightRange` 和 `specification` 只在订单级出现一次。`SCHEDULED` 订单还需提交 `scheduledPickupStartAt`、`scheduledPickupEndAt`,`NOW` 订单无需提交这两个字段。 创建订单使用 `POST /system/flashDelivery/v2/orders`。body 包含报价时的业务字段和完整 `stops`,再增加: ```json { "clientRequestId": "客户端生成并持久保存的本次下单请求号", "pricingId": 123, "pricingVersion": 1, "quotedBaseDeliveryFee": 80, "quotedDistanceFee": 10, "quotedUrgentFee": 0, "quotedAmount": 90, "pinRequired": true, "senderImageUrls": [], "userNote": "请注意文件袋" } ``` 上述价格数字只展示字段形状,实际值必须取本次报价 `data` 的 `pricingId`、`pricingVersion`、`baseDeliveryFee`、`distanceFee`、`urgentFee`、`amount`,不可使用示例数字。`clientRequestId` 最长 64 字符;同一次创建的网络重试使用相同值,新订单使用新值。`pinRequired` 省略时默认为 `true`,开启后每个收货站有独立四位收货码。报价在创建前变化时,接口会返回错误并在 `data` 附最新报价;展示新金额让用户重新确认后再提交。 ## 订单详情、收件人和进度 - 用户列表:`GET /system/flashDelivery/orders?role=sender|receiver&page=1&size=10`;`receiver` 包括每一个已匹配到系统普通用户的收货站。收件人列表卡片展示自己的收货地址。 - 用户详情:`GET /system/flashDelivery/orders/{id}`;按订单号打开消息:`GET /system/flashDelivery/orders/byOrderNo?orderNo=...`。 - 骑手列表与详情:`GET /system/flashDelivery/rider/orders`、`GET /system/flashDelivery/rider/orders/{id}`,沿用原有 `tab`、分页和可选坐标参数。 详情 `data.stops` 按路线顺序返回。请求站点字段叫 `type`,响应字段叫 `stopType`;响应的 `id` 才是后续履约所需的 `stopId`,`stopOrder` 从 0 开始。每站有 `status`、`remark`、`pickedUpAt`/`deliveredAt` 和 `imageUrls`。列表与详情提供 `pickupStopCount`、`pickedUpStopCount`、`deliveryStopCount`、`deliveredStopCount`。部分取货时整单仍为 `ACCEPTED`;全部取完为 `PICKED_UP`;部分送达仍为 `PICKED_UP`;全部送达为 `DELIVERED`;确认收货或平台/系统完成后为 `COMPLETED`。页面不要只靠整单 `status` 推算逐站进度。 v2 的订单级 `deliveryPinCode` 为空,收货码在各个 DELIVERY 站的 `stops[].deliveryPinCode`。发起人可查看全部收货码;已在系统内匹配的收件人只能查看自己站的收货码及联系人、精确地址和凭证;骑手看不到任何实际收货码。未匹配系统用户的收货站仍可下单,但此人不会进入“我收的”列表,也不会收到 App 推送;发起人需自行告知该站收货码。**逐站收件人通知本期暂缓。** 用户端取消按钮以 `canUserCancel` 为准;任一取货站已取货后不可自助取消。确认收货入口只在整单 `DELIVERED` 后提供;系统仍会检查全部收货站是否真的已送达。骑手收入账单只在订单进入 `COMPLETED` 时生成一次。 ## 骑手端:目标、取货、送达 骑手从详情 `currentTargetStopId` 定位当前站点;默认依次指向最早未完成的取货站,全部取完后依次指向最早未送达的收货站。操作成功后重新请求详情,不继续使用旧的目标 ID、状态或版本。 | 操作 | 当前接口 | 提交内容 | | --- | --- | --- | | 逐站取货 | `POST /system/flashDelivery/rider/orders/{id}/stops/{stopId}/pickup` | 现有接口,body 为 `{"imageUrls":["/profile/pickup.jpg"]}`;如同时传 `stopId`,必须与路径一致。 | | 选择目标站 | `POST /system/flashDelivery/rider/orders/{id}/stops/{stopId}/target` | 现有接口,body 含 `orderVersion`、`clientRequestId`,改变默认目标时还需 `reason` 和 `consentDeclared:true`。 | | 逐站送达 | `POST /system/flashDelivery/rider/orders/stops/deliver` | **新接口**,body 含 `orderId`、`stopId`、`imageUrls`,启用 PIN 时再传该站 `pinCode`。 | 取货和送达凭证每站至少 1 张、最多 9 张。新送达接口中 `orderId`、`stopId` 均为正整数;**只有一个收货站的 v2 订单也必须传 `stopId`**。骑手在送达现场向收件人核对其本站收货码,不能使用别站或订单级收货码。新骑手端可统一根据详情中的站点 ID 使用逐站送达接口;旧 `/rider/orders/{id}/deliver` 只供旧客户端处理旧订单,v2 订单调用它会被拒绝。 送达非当前目标站会被拒绝。改变顺序时,先取得发起人同意,再使用目标调整接口登记原因和 `consentDeclared:true`;该字段是骑手声明,当前没有发起人 App 内审批流程。调整请求的 `clientRequestId` 在同一次操作重试时保持不变。某站已成功取货或送达的重试不会重复保存照片和进度;末站送达后主状态已变化时也是如此。 ## 用户端:v2 待接单改址 仅待接单、未分配骑手的 v2 订单可编辑。先从详情读取 `orderVersion` 和完整站点,编辑后提交所有取货与收货站;只提交被修改的一站会丢失其余站点,因此服务端要求完整数组。 ```http POST /system/flashDelivery/v2/orders/address/quote token: <登录 token> Content-Type: application/json ``` ```json {"orderId":10,"orderVersion":0,"stops":[{"type":"PICKUP","name":"...","phone":"...","address":"...","longitude":121.56,"latitude":25.03},{"type":"DELIVERY","name":"...","phone":"...","address":"...","longitude":121.57,"latitude":25.04}]} ``` 确认保存调用 `POST /system/flashDelivery/v2/orders/address`,body 保留相同的 `orderId`、`orderVersion`、**完整** `stops`,再回传报价 `data` 中的 `distanceMeters`→`quotedDistanceMeters`、`baseDeliveryFee`→`quotedBaseDeliveryFee`、`distanceFee`→`quotedDistanceFee`、`urgentFee`→`quotedUrgentFee`、`amount`→`quotedAmount`。只改站点,物品、配送方式、小费和原运价快照继续沿用订单保存值。报价或版本变化时重新获取并让用户确认。保存成功后以返回详情更新全部 `stops[].id` 和各站收货码;旧站点 ID 已失效。旧改址入口会拒绝 v2 订单,旧订单仍沿用原入口。 ## 发起人逐站通知 每个取货/收货站首次成功且事务提交后,服务端向**订单发起人**推送并写入消息中心。两种 `eventType`:`FLASH_PICKUP_STOP_COMPLETED`、`FLASH_DELIVERY_STOP_COMPLETED`。消息 payload 示例: ```json { "ddId": "订单号", "state": "PICKED_UP", "type": -1, "pushType": 3, "eventType": "FLASH_PICKUP_STOP_COMPLETED", "stopId": 13, "stopType": "PICKUP", "stopOrder": 0, "completedStopCount": 2, "totalStopCount": 2 } ``` `state` 是通知发生时真实的整单状态,不应写死为示例值;`stopOrder` 为路线数组的 0 基下标,通知文案的站号为 1 基。收到事件后按 `ddId` 请求 `/orders/byOrderNo` 并刷新详情;不要从消息体推断联系人、地址或 PIN。相同站点的重试不重复通知,最后一站也不叠发旧整单阶段通知。逐站收件人通知留待后续需求。 ## 联调验收重点 1. 单取单送和多取多送都走 v2 报价/创建;收货站只有一站时,送达仍提供 `stopId`。 2. 每站首次取货/送达后进度计数与当前目标正确;部分送达时整单仍为 `PICKED_UP`,不能提前确认完成或记账。 3. 修改第二个收货站后,其他站、订单级物品和原运价仍在;保存后旧站点 ID/PIN 不再使用。 4. 发起人看全部收货码,匹配的第二站收件人只看自己的地址、联系人、凭证和 PIN;骑手看不到 PIN;未匹配用户不出现 App 收件人入口。 5. 发起人收到每站一次进度消息并能按订单号打开详情;收件人没有逐站推送。真实设备推送和 App 页面尚需联调。 现有单点、支付及其他旧接口以 [原闪送 App API 文档](flash-delivery-app-api.md) 为准。数据库结构和不同环境的执行方式见 [10 月 14 日 SQL 说明](../updatesql/sql.md);本次问题修复没有额外字段变更;2026-10-14 测试库已按旧版增量路径完成结构与字典更新,其他环境仍需按 SQL 说明执行。