
随着外卖业务不断发展,平台除了需要具备用户下单、商家接单、订单管理等基础能力,还需要解决一个核心问题:订单如何快速、高效地进入配送环节。
对于自建外卖平台、同城配送平台以及连锁餐饮系统来说,直接自建完整的配送体系往往需要投入较多的人力和技术成本。因此,越来越多外卖系统会通过接入第三方配送API,将订单系统与配送服务进行连接,实现订单自动创建配送任务、配送状态实时同步以及骑手信息回传。
本文将从系统架构、接口设计、核心流程以及代码实现几个方面,介绍外卖系统接入配送API的一套完整方案。

配送API可以理解为外卖系统与配送服务之间的数据桥梁。
用户完成下单后,外卖系统产生订单数据,然后通过API将订单信息提交给配送服务。配送服务根据配送地址、订单重量、配送距离等信息创建配送任务,并将配送状态返回给外卖系统。
基本流程可以简化为:
用户下单
↓
外卖系统生成订单
↓
商家确认接单
↓
调用配送API
↓
创建配送任务
↓
配送服务派单
↓
骑手接单
↓
骑手取货
↓
配送中
↓
配送完成
↓
配送状态回传外卖系统这样可以将订单系统与配送系统进行解耦,让外卖平台专注于业务管理,而配送服务负责具体履约。
在实际开发过程中,可以将系统拆分成用户端、商家端、配送服务以及平台后台几个部分。
┌──────────────┐
│ 用户端 │
│ H5 / 小程序 / APP │
└──────┬───────┘
│ 下单
↓
┌──────────────┐
│ 外卖业务系统 │
│ PHP + MySQL │
└──────┬───────┘
│
│ 配送API
↓
┌──────────────┐
│ 配送服务平台 │
└──────┬───────┘
│
│ 派单
↓
┌──────────────┐
│ 骑手端 │
└──────┬───────┘
│
│ 状态回传
↓
┌──────────────┐
│ 外卖系统后台 │
└──────────────┘其中最重要的是中间的配送API服务层。
建议不要让订单业务代码直接大量依赖第三方接口,而是单独封装一个配送服务类。
例如:
interface DeliveryServiceInterface
{
public function createOrder(array $order);
public function cancelOrder(string $deliveryNo);
public function queryOrder(string $deliveryNo);
}后续如果需要更换配送服务,只需要替换具体实现即可,不需要大面积修改订单业务代码。
一个完整的外卖配送接口体系通常包含以下几个核心功能。
商家确认订单后,外卖系统向配送服务提交配送任务。
通常需要传递:
{
"order_no": "WM202609020001",
"shop_name": "XX餐饮店",
"shop_phone": "13800000000",
"shop_address": "XX路100号",
"user_name": "张先生",
"user_phone": "13900000000",
"user_address": "XX小区5号楼",
"goods_amount": 38.5,
"delivery_fee": 5,
"remark": "请放在门口"
}配送平台成功创建后,会返回一个配送单号。
{
"code": 0,
"message": "success",
"delivery_no": "PS202609020001"
}外卖系统需要保存这个配送单号,用于后续查询和状态同步。
当平台需要查看当前配送状态时,可以调用配送查询接口。
例如:
GET /api/delivery/order/detail请求:
{
"delivery_no": "PS202609020001"
}返回:
{
"delivery_no": "PS202609020001",
"status": "delivering",
"rider_name": "李师傅",
"rider_phone": "13812345678"
}然后将配送状态同步到外卖订单表。
以PHP为例,可以对HTTP请求进行统一封装。
class DeliveryApi
{
private string $baseUrl;
private string $appKey;
private string $appSecret;
public function __construct()
{
$this->baseUrl = 'https://api.example.com';
$this->appKey = 'your_app_key';
$this->appSecret = 'your_app_secret';
}
private function request(string $method, string $uri, array $data = [])
{
$timestamp = time();
$signString = $this->appKey
. $timestamp
. json_encode($data, JSON_UNESCAPED_UNICODE)
. $this->appSecret;
$sign = hash('sha256', $signString);
$headers = [
'Content-Type: application/json',
'X-App-Key: ' . $this->appKey,
'X-Timestamp: ' . $timestamp,
'X-Sign: ' . $sign
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->baseUrl . $uri);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt(
$ch,
CURLOPT_POSTFIELDS,
json_encode($data, JSON_UNESCAPED_UNICODE)
);
}
$response = curl_exec($ch);
if ($response === false) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
return json_decode($response, true);
}
public function createOrder(array $order)
{
return $this->request(
'POST',
'/api/delivery/order/create',
$order
);
}
}实际项目中,baseUrl、appKey、appSecret等信息应该放在环境变量或者系统配置中,不建议直接写死在业务代码里。
当商家确认订单后,可以执行创建配送任务。
例如订单数据:
$order = [
'order_no' => 'WM202609020001',
'shop_name' => 'XX餐饮店',
'shop_phone' => '13800000000',
'shop_address' => 'XX路100号',
'user_name' => '张先生',
'user_phone' => '13900000000',
'user_address' => 'XX小区5号楼',
'goods_amount' => 38.5,
'delivery_fee' => 5,
'remark' => '请放在门口'
];
$deliveryApi = new DeliveryApi();
$result = $deliveryApi->createOrder($order);
if ($result['code'] === 0) {
// 保存配送单号
$deliveryNo = $result['delivery_no'];
// 更新订单配送信息
saveDeliveryNo(
$order['order_no'],
$deliveryNo
);
}数据库可以设计一个配送信息表:
CREATE TABLE `order_delivery` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`order_id` BIGINT UNSIGNED NOT NULL,
`order_no` VARCHAR(64) NOT NULL,
`delivery_no` VARCHAR(64) DEFAULT NULL,
`rider_name` VARCHAR(50) DEFAULT NULL,
`rider_phone` VARCHAR(30) DEFAULT NULL,
`delivery_status` VARCHAR(30) DEFAULT 'pending',
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这样可以把订单业务数据和配送业务数据进行分离。
外卖系统接入配送API时,不能只依靠主动查询。
更合理的方式是:
配送平台
↓
状态发生变化
↓
调用外卖系统回调接口
↓
外卖系统验证请求
↓
更新订单配送状态
↓
更新用户端订单页面例如配送状态:
pending 待配送
accepted 骑手已接单
picked_up 已取货
delivering 配送中
completed 配送完成
cancelled 配送取消配送平台可以向:
POST /api/delivery/callback发送:
{
"delivery_no": "PS202609020001",
"order_no": "WM202609020001",
"status": "delivering",
"rider_name": "李师傅",
"rider_phone": "13812345678",
"timestamp": 1788326400
}后端接收到回调后,需要先验证签名,再处理订单。
public function callback()
{
$body = file_get_contents('php://input');
$data = json_decode($body, true);
if (!$data) {
return json([
'code' => 400,
'message' => 'invalid request'
]);
}
// 验证签名
if (!$this->verifySign($data)) {
return json([
'code' => 401,
'message' => 'invalid sign'
]);
}
$deliveryNo = $data['delivery_no'];
$status = $data['status'];
$delivery = DeliveryOrder::where(
'delivery_no',
$deliveryNo
)->first();
if (!$delivery) {
return json([
'code' => 404,
'message' => 'delivery order not found'
]);
}
$delivery->delivery_status = $status;
$delivery->rider_name = $data['rider_name'] ?? '';
$delivery->rider_phone = $data['rider_phone'] ?? '';
$delivery->updated_at = date('Y-m-d H:i:s');
$delivery->save();
// 同步外卖订单状态
$this->syncOrderStatus(
$delivery->order_no,
$status
);
return json([
'code' => 0,
'message' => 'success'
]);
}这里需要特别注意幂等处理。
例如配送平台因为网络问题重复发送3次“配送完成”通知,外卖系统不能因此重复执行订单完成后的业务逻辑。
可以通过配送单号+状态+事件ID等方式实现幂等。
配送状态更新后,可以进一步同步到用户端。
例如:
商家已接单
↓
等待骑手接单
↓
骑手已接单
↓
骑手正在取货
↓
骑手配送中
↓
订单已送达如果系统使用WebSocket,可以在状态发生变化时实时推送:
$data = [
'type' => 'delivery_status',
'order_no' => 'WM202609020001',
'status' => 'delivering',
'rider_name' => '李师傅'
];
WebSocket::push(
$userId,
json_encode($data, JSON_UNESCAPED_UNICODE)
);如果没有WebSocket,也可以采用短轮询方式,让用户端定时查询订单状态。
配送费用通常与配送距离存在直接关系。
因此外卖系统在设计配送模块时,可以将配送距离、配送区域、基础配送费以及额外费用进行拆分。
例如:
配送费 =
基础配送费
+ 距离费用
+ 时段附加费
+ 特殊区域费用后端可以抽象成一个配送费计算方法:
function calculateDeliveryFee(
float $baseFee,
float $distance,
float $distanceUnitPrice,
float $timeFee = 0,
float $areaFee = 0
): float {
$distanceFee = 0;
if ($distance > 3) {
$extraDistance = $distance - 3;
$distanceFee = $extraDistance * $distanceUnitPrice;
}
return round(
$baseFee
+ $distanceFee
+ $timeFee
+ $areaFee,
2
);
}例如:
$fee = calculateDeliveryFee(
5,
6.5,
1.5,
2,
0
);
echo $fee;如果基础配送费为5元,3公里以后每公里1.5元,当前距离6.5公里,并存在2元时段附加费,则可以根据业务规则计算最终配送费用。
实际项目中,费用规则应当放在后台配置,而不是固定写在代码里。
API对接最容易被忽略的就是异常情况。
例如:
接口超时
接口返回错误
配送区域不支持
配送服务暂停
骑手无法接单
订单创建失败
订单取消失败
回调重复
回调丢失
网络异常因此建议增加重试机制。
例如:
function requestWithRetry(callable $request, int $maxRetry = 3)
{
$retry = 0;
while ($retry < $maxRetry) {
try {
$result = $request();
if (
isset($result['code'])
&& $result['code'] === 0
) {
return $result;
}
} catch (Throwable $e) {
// 写入日志
error_log($e->getMessage());
}
$retry++;
sleep(2);
}
throw new Exception(
'配送API请求失败'
);
}不过需要注意,创建订单类接口不能简单地无限重试。
否则可能出现:
第一次请求成功
↓
外卖系统没有收到响应
↓
系统再次创建配送订单
↓
产生两个配送任务因此创建配送任务时应该使用业务订单号作为幂等键。
如果未来可能接入多个配送服务,可以进一步设计统一配送接口。
例如:
interface DeliveryInterface
{
public function create(array $order);
public function cancel(string $deliveryNo);
public function detail(string $deliveryNo);
public function calculateFee(array $params);
}不同配送服务分别实现:
class DeliveryProviderA implements DeliveryInterface
{
public function create(array $order)
{
// 服务A接口
}
public function cancel(string $deliveryNo)
{
// 服务A取消接口
}
public function detail(string $deliveryNo)
{
// 服务A查询接口
}
public function calculateFee(array $params)
{
// 服务A计费接口
}
}另外一个配送服务:
class DeliveryProviderB implements DeliveryInterface
{
public function create(array $order)
{
// 服务B接口
}
public function cancel(string $deliveryNo)
{
// 服务B取消接口
}
public function detail(string $deliveryNo)
{
// 服务B查询接口
}
public function calculateFee(array $params)
{
// 服务B计费接口
}
}业务层只调用:
$deliveryService->create($order);而不需要关心具体使用哪一家配送服务。
这种架构对于后续扩展配送渠道非常有帮助。
从实际开发角度来看,配送API对接并不是简单地调用几个HTTP接口,而是需要考虑整个订单生命周期。
核心需要解决以下几个问题:
外卖系统中的订单字段与配送平台字段可能不同,需要建立数据映射关系。
避免因为网络重试造成重复配送。
不同配送平台可能采用不同的状态名称,需要转换成外卖系统自己的标准状态。
所有回调接口都应该进行签名验证,防止非法请求修改订单状态。
网络超时、接口异常等情况需要支持自动重试和人工补偿。
建议记录:
订单号
配送单号
请求参数
响应数据
请求时间
响应时间
接口耗时
错误信息
重试次数方便出现问题时快速定位。
最终,一个较为完整的外卖配送业务流程可以设计为:
用户提交订单
↓
订单支付成功
↓
商家接单
↓
计算配送费用
↓
创建配送任务
↓
配送平台接收订单
↓
骑手接单
↓
骑手到店取货
↓
开始配送
↓
配送状态实时同步
↓
用户收到商品
↓
配送完成
↓
外卖订单完成与此同时,平台后台可以通过数据看板统计:
配送订单量
配送完成率
平均配送时长
平均配送距离
配送取消率
骑手接单率
异常订单数量通过这些数据,可以进一步优化配送区域、配送规则以及骑手调度策略。

外卖系统接入配送API,本质上是通过标准化接口打通订单系统与配送服务,让订单从“商家接单”能够自动进入“配送履约”环节。
在开发过程中,除了完成创建配送订单、查询配送状态、取消配送以及状态回调等基础接口,还需要重点考虑接口安全、订单幂等、异常重试、数据同步以及多配送渠道适配等问题。
对于需要搭建外卖平台的企业来说,更合理的方式不是简单增加一个配送接口,而是将配送能力设计成独立的服务模块。这样既能够降低系统之间的耦合,也方便后续扩展不同配送渠道。
最终形成:
用户下单
↓
商家接单
↓
订单中心
↓
配送服务
↓
骑手履约
↓
状态回传
↓
用户收货通过外卖系统与配送API的深度结合,可以进一步实现订单、商家、配送、骑手和用户之间的数据互通,为外卖平台建立更加完整的订单履约体系。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。