随着外卖、同城零售以及本地生活服务的发展,订单履约已经成为平台运营中的重要环节。
传统外卖系统通常需要用户端、商家端、骑手端以及管理后台共同协作。当用户完成下单后,商家接单、配送派单、骑手取货、配送中、订单完成等环节都需要进行数据同步。
如果配送体系与订单系统相互独立,就容易出现订单状态不同步、配送信息更新滞后、人工派单效率低等问题。
因此,在外卖系统开发过程中,通过接入配送API,将订单中心与配送服务进行连接,可以让订单创建、配送派单、骑手状态以及配送结果形成完整的数据闭环。
本文将从系统架构、API设计、数据库设计以及核心代码几个方面,对外卖系统接入配送API的实现方案进行解析。
一、什么是外卖系统配送API
配送API主要用于连接外卖系统和第三方配送服务。
外卖系统负责用户下单、商家接单、订单管理等业务,而配送API负责将订单信息传递给配送服务,并接收配送服务返回的配送状态。
整体流程可以理解为:
用户端
↓
提交订单
↓
订单中心
↓
商家确认订单
↓
配送API
↓
创建配送任务
↓
配送服务
↓
骑手接单
↓
取货配送
↓
配送完成
↓
配送状态回传
↓
外卖订单更新
通过这种方式,可以把“订单管理”和“配送履约”连接起来。
二、外卖系统接入配送API的整体架构
一个比较完整的外卖配送架构,可以划分为以下几个模块:
┌────────────────────┐
│ 用户端 │
│ H5 / 小程序 / APP │
└─────────┬──────────┘
│
↓
┌────────────────────┐
│ 订单中心 │
│ 用户订单 / 商家订单 │
└─────────┬──────────┘
│
↓
┌────────────────────┐
│ 配送服务层 │
│ Delivery Service │
└─────────┬──────────┘
│
┌────┴─────┐
↓ ↓
配送API A 配送API B
│ │
└────┬─────┘
↓
配送服务
↓
骑手端
这里建议增加一个独立的“配送服务层”,而不是让订单业务直接调用第三方API。
这样做的好处是,当以后需要增加新的配送渠道时,不需要修改大量订单业务代码。
三、为什么需要独立的配送服务层
例如订单业务直接写成:
$api = new DeliveryApi();
$api->createOrder($order);
初期看起来比较简单,但是当平台同时接入多个配送服务后,代码可能逐渐变成:
if ($provider == 'A') {
// 调用配送平台A
} elseif ($provider == 'B') {
// 调用配送平台B
} elseif ($provider == 'C') {
// 调用配送平台C
}
随着业务增长,这种方式会增加代码维护难度。
更合理的方式是建立统一接口。
interface DeliveryInterface
{
public function createOrder(array $order);
public function cancelOrder(string $deliveryNo);
public function getOrder(string $deliveryNo);
public function getDeliveryFee(array $params);
}
然后不同配送服务分别实现这个接口。
class ProviderA implements DeliveryInterface
{
public function createOrder(array $order)
{
// 调用配送服务A
}
public function cancelOrder(string $deliveryNo)
{
// 取消配送服务A订单
}
public function getOrder(string $deliveryNo)
{
// 查询配送服务A订单
}
public function getDeliveryFee(array $params)
{
// 计算配送费用
}
}
这样订单系统只需要调用统一方法:
$delivery->createOrder($order);
而不需要关心具体使用哪一个配送服务。
四、配送API需要设计哪些核心接口
外卖系统接入配送API时,通常需要围绕订单生命周期设计接口。
1. 配送费用查询
用户下单之前,可以根据商家地址和用户收货地址计算配送费用。
例如:
POST /api/delivery/fee
请求参数:
{
"shop_lat": 45.123456,
"shop_lng": 126.123456,
"user_lat": 45.234567,
"user_lng": 126.234567
}
返回:
{
"code": 0,
"delivery_fee": 6.00,
"distance": 4.8
}
外卖系统可以根据返回结果向用户展示配送费用。
2. 创建配送订单
商家确认订单后,创建配送任务。
POST /api/delivery/order/create
请求:
{
"order_no": "WM202609030001",
"shop_name": "XX餐饮店",
"shop_phone": "13800000000",
"shop_address": "中央大街100号",
"user_name": "张先生",
"user_phone": "13900000000",
"user_address": "幸福小区5号楼",
"goods_amount": 58.00,
"delivery_fee": 6.00,
"remark": "请提前联系"
}
返回:
{
"code": 0,
"message": "success",
"delivery_no": "PS202609030001"
}
外卖系统需要将delivery_no保存到数据库。
3. 查询配送订单
GET /api/delivery/order/detail
请求:
{
"delivery_no": "PS202609030001"
}
返回:
{
"code": 0,
"delivery_no": "PS202609030001",
"status": "delivering",
"rider_name": "李师傅",
"rider_phone": "13812345678"
}
4. 取消配送订单
如果用户取消订单或者商家无法正常出餐,可以调用取消配送接口。
POST /api/delivery/order/cancel
请求:
{
"order_no": "WM202609030001",
"delivery_no": "PS202609030001",
"reason": "用户取消订单"
}
五、PHP实现配送API请求封装
以PHP为例,可以将HTTP请求统一封装。
class DeliveryClient
{
private string $baseUrl;
private string $appKey;
private string $appSecret;
public function __construct()
{
$this->baseUrl = 'https://api.example.com';
$this->appKey = getenv('DELIVERY_APP_KEY');
$this->appSecret = getenv('DELIVERY_APP_SECRET');
}
public function post(string $path, array $data)
{
$timestamp = time();
$body = json_encode(
$data,
JSON_UNESCAPED_UNICODE
);
$sign = hash(
'sha256',
$this->appKey .
$timestamp .
$body .
$this->appSecret
);
$headers = [
'Content-Type: application/json',
'X-App-Key: ' . $this->appKey,
'X-Timestamp: ' . $timestamp,
'X-Sign: ' . $sign
];
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $this->baseUrl . $path,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10
]);
$result = curl_exec($ch);
if ($result === false) {
throw new RuntimeException(
curl_error($ch)
);
}
curl_close($ch);
return json_decode($result, true);
}
}
这里将APP_KEY和APP_SECRET放到环境变量中,可以避免把敏感配置直接写在源代码里。
六、创建配送订单
当商家完成接单后,可以创建配送订单。
$order = [
'order_no' => 'WM202609030001',
'shop_name' => 'XX餐饮店',
'shop_phone' => '13800000000',
'shop_address' => '中央大街100号',
'user_name' => '张先生',
'user_phone' => '13900000000',
'user_address' => '幸福小区5号楼',
'goods_amount' => 58,
'delivery_fee' => 6,
'remark' => '请提前联系'
];
$client = new DeliveryClient();
$result = $client->post(
'/api/delivery/order/create',
$order
);
if (($result['code'] ?? -1) === 0) {
$deliveryNo = $result['delivery_no'];
// 保存配送单号
saveDeliveryOrder(
$order['order_no'],
$deliveryNo
);
}
这里最重要的是建立“外卖订单号”和“配送订单号”的关联关系。
例如:
外卖订单号:
WM202609030001
配送订单号:
PS202609030001
后续查询配送状态、取消配送以及处理回调时,都可以通过这个关系找到对应订单。
七、数据库设计
可以单独建立配送订单表。
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,
`provider` VARCHAR(32) DEFAULT NULL,
`status` VARCHAR(32) DEFAULT 'pending',
`rider_name` VARCHAR(50) DEFAULT NULL,
`rider_phone` VARCHAR(30) DEFAULT NULL,
`distance` DECIMAL(10,2) DEFAULT 0,
`delivery_fee` DECIMAL(10,2) DEFAULT 0,
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_delivery_no` (`delivery_no`)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4;
通过单独的配送表,可以避免将大量配送字段直接堆积到订单表中。
八、配送状态统一
不同配送服务的状态名称可能并不一致。
例如服务A可能返回:
WAITING
TAKEN
PICKED
DELIVERING
FINISHED
服务B可能返回:
pending
accepted
picked_up
delivering
completed
因此系统内部最好建立统一状态。
例如:
const DELIVERY_PENDING = 'pending';
const DELIVERY_ACCEPTED = 'accepted';
const DELIVERY_PICKED_UP = 'picked_up';
const DELIVERY_DELIVERING = 'delivering';
const DELIVERY_COMPLETED = 'completed';
const DELIVERY_CANCELLED = 'cancelled';
然后进行状态转换:
function normalizeStatus(string $status): string
{
$map = [
'WAITING' => 'pending',
'TAKEN' => 'accepted',
'PICKED' => 'picked_up',
'DELIVERING' => 'delivering',
'FINISHED' => 'completed'
];
return $map[$status] ?? 'unknown';
}
这样无论后台接入多少配送服务,用户端看到的订单状态都是统一的。
九、通过回调实现配送状态同步
配送API对接中,一个非常重要的功能就是回调。
例如骑手已经接单,配送服务主动通知外卖系统:
POST /api/delivery/callback
请求:
{
"delivery_no": "PS202609030001",
"order_no": "WM202609030001",
"status": "accepted",
"rider_name": "李师傅",
"rider_phone": "13812345678",
"event_id": "EV202609030001"
}
后端收到之后:
public function callback()
{
$body = file_get_contents(
'php://input'
);
$data = json_decode(
$body,
true
);
if (!$data) {
return response()->json([
'code' => 400,
'message' => 'Invalid request'
]);
}
if (!$this->verifySignature($data)) {
return response()->json([
'code' => 401,
'message' => 'Invalid signature'
]);
}
$deliveryNo = $data['delivery_no'];
$status = normalizeStatus(
$data['status']
);
$delivery = DeliveryOrder::where(
'delivery_no',
$deliveryNo
)->first();
if (!$delivery) {
return response()->json([
'code' => 404,
'message' => 'Order not found'
]);
}
$delivery->status = $status;
$delivery->rider_name =
$data['rider_name'] ?? '';
$delivery->rider_phone =
$data['rider_phone'] ?? '';
$delivery->save();
$this->syncOrderStatus(
$delivery->order_no,
$status
);
return response()->json([
'code' => 0,
'message' => 'success'
]);
}
这样配送平台发生状态变化时,外卖系统就能够及时更新。
十、为什么必须处理回调幂等
实际网络环境中,一个回调可能被重复发送。
例如:
配送平台
↓
发送“配送完成”
↓
外卖系统已经处理
↓
网络响应失败
↓
配送平台再次发送
↓
外卖系统再次处理
如果没有幂等机制,就可能出现重复结算、重复发送通知等问题。
可以利用事件ID建立唯一索引。
ALTER TABLE `delivery_event`
ADD UNIQUE KEY `uk_event_id` (`event_id`);
处理回调时:
$exists = DeliveryEvent::where(
'event_id',
$data['event_id']
)->exists();
if ($exists) {
return response()->json([
'code' => 0,
'message' => 'already processed'
]);
}
然后再保存事件:
DeliveryEvent::create([
'event_id' => $data['event_id'],
'delivery_no' => $data['delivery_no'],
'status' => $data['status']
]);
这样即使同一个回调发送多次,也只会执行一次业务逻辑。
十一、配送费用计算
除了配送订单创建之外,配送费用也是外卖系统的重要组成部分。
可以根据业务规则设计:
配送费 =
基础配送费
+
距离费用
+
高峰时段附加费
+
特殊区域费用
例如:
function calculateDeliveryFee(
float $baseFee,
float $distance,
float $unitPrice,
float $timeFee = 0,
float $areaFee = 0
): float {
$distanceFee = 0;
$freeDistance = 3;
if ($distance > $freeDistance) {
$extraDistance =
$distance - $freeDistance;
$distanceFee =
$extraDistance * $unitPrice;
}
return round(
$baseFee
+ $distanceFee
+ $timeFee
+ $areaFee,
2
);
}
例如:
$fee = calculateDeliveryFee(
5,
6,
1.5,
2,
0
);
echo $fee;
在实际系统中,可以将这些参数放到后台配置中,让平台运营人员根据配送区域、时间段以及业务规则进行调整。
十二、配送异常处理
API并不是每一次调用都能够成功。
系统需要考虑:
接口超时
网络异常
参数错误
配送区域不支持
骑手无法接单
配送服务繁忙
配送订单取消失败
配送订单重复创建
回调通知失败
因此建议建立配送任务状态:
待创建
创建中
创建成功
创建失败
配送中
配送完成
取消中
已取消
异常
同时建立API日志:
CREATE TABLE `delivery_api_log` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`order_no` VARCHAR(64) DEFAULT NULL,
`delivery_no` VARCHAR(64) DEFAULT NULL,
`api_path` VARCHAR(255) NOT NULL,
`request_data` TEXT,
`response_data` TEXT,
`http_code` INT DEFAULT 0,
`cost_time` INT DEFAULT 0,
`created_at` DATETIME NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4;
通过日志可以快速定位:
哪个订单出了问题
调用了哪个接口
发送了什么参数
第三方返回了什么结果
接口耗时多久
是否发生重试
十三、使用队列提升系统稳定性
如果订单量比较大,不建议让用户下单请求直接等待配送API响应。
例如:
用户支付
↓
订单生成
↓
提交配送任务
↓
立即返回
可以改成:
用户支付
↓
订单生成
↓
创建配送任务
↓
消息队列
↓
配送服务Worker
↓
调用配送API
例如:
CreateDeliveryJob::dispatch(
$order->id
);
Worker执行:
class CreateDeliveryJob
{
public function handle()
{
$order = Order::find(
$this->orderId
);
if (!$order) {
return;
}
$delivery = app(
DeliveryService::class
);
$delivery->createOrder(
$order
);
}
}
这样可以降低第三方API响应速度对订单系统的影响。
十四、配送轨迹数据
如果配送服务能够提供骑手位置或配送轨迹接口,外卖系统还可以进一步实现配送地图。
例如:
{
"rider_name": "李师傅",
"status": "delivering",
"latitude": 45.123456,
"longitude": 126.123456,
"updated_at": "2026-09-03 14:20:00"
}
用户端可以根据经纬度展示:
商家位置
↓
骑手当前位置
↓
用户收货位置
从而实现类似“骑手实时配送”的功能。
需要注意的是,位置数据更新频率、存储策略以及隐私保护都应该根据具体配送服务的能力和业务需求进行设计。
十五、后台如何管理配送订单
对于平台管理后台,可以设计独立的配送管理模块。
例如:
配送管理
├── 配送订单
├── 待配送订单
├── 配送中订单
├── 已完成订单
├── 异常配送
├── 配送服务配置
├── 配送费用规则
├── API日志
└── 配送数据统计
配送订单列表可以展示:
订单编号
商家名称
用户信息
配送地址
配送费用
配送状态
骑手信息
创建时间
配送完成时间
这样运营人员可以从后台统一查看订单和配送情况。
十六、打造订单与配送一体化闭环
完成API接入后,可以形成完整的业务闭环:
用户下单
↓
订单创建
↓
支付成功
↓
商家接单
↓
配送费用确认
↓
创建配送任务
↓
配送平台派单
↓
骑手接单
↓
骑手取货
↓
配送中
↓
用户收货
↓
配送完成
↓
订单完成
↓
数据统计
与此同时,订单、配送、骑手以及用户端的数据可以进行统一管理。
十七、外卖系统接入配送API的核心价值
从系统建设角度来看,配送API并不仅仅是增加一个“配送接口”,而是将订单系统与履约体系连接起来。
通过合理设计,可以实现:
订单自动流转
商家确认订单后自动创建配送任务,减少人工操作。
配送状态同步
骑手接单、取货、配送以及完成等状态可以实时同步。
多配送渠道扩展
通过统一配送接口,可以根据业务需求扩展不同配送服务。
配送费用统一管理
平台可以根据距离、区域、时间段等规则进行配送费用管理。
异常订单统一处理
通过API日志、任务队列和异常状态,可以快速定位配送问题。
数据统一沉淀
订单、配送、骑手以及配送费用等数据可以在平台后台统一统计。

十八、总结
外卖系统接入配送API的核心目标,是打通订单与配送之间的数据链路,让用户下单、商家接单、配送派单、骑手履约以及订单完成形成完整闭环。
在具体开发过程中,可以采用“订单中心 + 配送服务层 + API适配器 + 回调机制 + 消息队列”的架构,将核心业务与第三方配送服务进行解耦。
一个成熟的外卖系统配送架构可以概括为:
订单中心
↓
配送服务层
↓
API适配器
↓
第三方配送服务
↓
骑手履约
↓
配送状态回调
↓
订单状态同步
↓
用户端 / 商家端 / 管理后台
这种架构不仅能够满足当前外卖订单的配送需求,也能够为后续扩展跑腿、同城配送、即时零售等业务提供基础。
因此,在进行外卖系统开发时,配送API最好不要作为一个简单的附加功能进行开发,而应该将其作为订单履约体系的重要组成部分,通过标准化接口、统一状态、幂等机制、异常处理和数据日志等技术手段,打造更加稳定、高效的订单与配送一体化管理平台。