首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >外卖系统对接三方接口方案:如何实现订单、支付与配送数据互通

外卖系统对接三方接口方案:如何实现订单、支付与配送数据互通

原创
作者头像
万岳教育Lili
发布2026-09-01 17:50:46
发布2026-09-01 17:50:46
40
举报

随着同城外卖、校园配送、商家自营配送等业务不断发展,外卖系统已经不再只是简单的“商品展示+下单+支付”。

一个相对完整的外卖系统,通常还需要与支付服务、配送服务、短信服务、地图服务以及其他第三方业务系统进行接口对接。

因此,在外卖系统定制开发过程中,“外卖系统对接三方接口”已经成为比较常见的技术需求。

尤其是订单、支付和配送三个环节,如果系统之间无法实现稳定的数据互通,就容易出现支付成功但订单状态没有更新、骑手配送状态不同步、订单重复推送等问题。

本文就从系统架构、接口设计、订单同步、支付回调以及配送状态同步几个方面,对外卖系统对接三方接口的实现方案进行介绍。

外卖系统
外卖系统

一、外卖系统为什么需要对接三方接口?

传统外卖系统可以将业务全部放在自己的服务器中完成。

例如:

用户选择商品 → 提交订单 → 系统生成订单 → 用户支付 → 商家接单 → 骑手配送 → 订单完成。

但实际业务中,很多环节需要依赖外部服务。

例如:

  • 支付需要调用第三方支付接口
  • 配送可能需要调用第三方配送接口
  • 地图服务需要调用地图API
  • 短信通知需要调用短信服务
  • 电子发票可能需要调用发票接口
  • 部分商家可能需要与自身ERP、POS等系统进行数据同步

因此,一个比较合理的系统架构应该将自身业务系统与第三方服务进行解耦。

可以采用下面这种结构:

代码语言:javascript
复制
                 ┌──────────────┐
                 │   用户端     │
                 │ H5 / 小程序  │
                 └──────┬───────┘
                        │
                        ▼
                ┌───────────────┐
                │   外卖业务系统 │
                │   PHP + MySQL │
                └───────┬───────┘
                        │
          ┌─────────────┼─────────────┐
          │             │             │
          ▼             ▼             ▼
     ┌─────────┐   ┌─────────┐   ┌─────────┐
     │支付接口 │   │配送接口 │   │地图接口 │
     └─────────┘   └─────────┘   └─────────┘

这样做的好处是,即使某一个第三方服务发生变化,也不会直接影响整个外卖系统的核心业务。

二、外卖系统对接三方接口的核心流程

以一个普通外卖订单为例,整个流程可以拆分为几个步骤。

代码语言:javascript
复制
用户提交订单
      ↓
系统创建本地订单
      ↓
生成待支付订单
      ↓
调用支付接口
      ↓
用户完成支付
      ↓
支付平台回调
      ↓
系统验证支付结果
      ↓
更新订单为已支付
      ↓
通知商家接单
      ↓
创建配送订单
      ↓
第三方配送平台接单
      ↓
同步配送状态
      ↓
骑手完成配送
      ↓
更新本地订单为已完成

这里有一个非常重要的设计原则:

第三方接口返回成功,并不代表整个业务流程已经完成。

系统必须以自己的订单状态作为最终业务依据。

三、订单系统如何设计?

首先需要建立自己的订单表。

例如MySQL可以设计为:

代码语言:javascript
复制
CREATE TABLE `orders` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `order_no` VARCHAR(64) NOT NULL,
    `user_id` BIGINT NOT NULL,
    `shop_id` BIGINT NOT NULL,
    `total_amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
    `pay_amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
    `pay_status` TINYINT NOT NULL DEFAULT 0,
    `order_status` TINYINT NOT NULL DEFAULT 0,
    `delivery_status` TINYINT NOT NULL DEFAULT 0,
    `third_order_no` VARCHAR(128) DEFAULT NULL,
    `created_at` DATETIME NOT NULL,
    `updated_at` DATETIME NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_order_no` (`order_no`)
);

其中:

代码语言:javascript
复制
pay_status
0 = 未支付
1 = 已支付
2 = 已退款

order_status
0 = 待支付
1 = 待接单
2 = 配送中
3 = 已完成
4 = 已取消

delivery_status
0 = 未创建
1 = 已创建
2 = 配送中
3 = 已送达
4 = 配送异常

实际项目中可以根据具体业务增加更多状态。

例如:

代码语言:javascript
复制
商家已接单
骑手已接单
骑手已到店
骑手取餐
骑手送达
用户确认收货

四、订单号设计

订单号是整个系统与第三方进行数据关联的重要字段。

例如PHP可以这样生成订单号:

代码语言:javascript
复制
function createOrderNo()
{
    return date('YmdHis') . mt_rand(100000, 999999);
}

$orderNo = createOrderNo();

echo $orderNo;

生成结果类似:

代码语言:javascript
复制
20260901172535123456

建议不要直接使用数据库自增ID作为对外订单号。

可以使用:

代码语言:javascript
复制
业务订单号
+
数据库ID
+
第三方订单号

分别管理。

例如:

代码语言:javascript
复制
本地订单号:20260901172535123456

数据库ID:10235

第三方订单号:THIRD202609010001

这样后续排查接口问题会更加方便。

五、支付接口如何对接?

支付接口一般分为两个阶段:

第一阶段是创建支付订单。

第二阶段是接收支付回调。

例如后端创建支付订单:

代码语言:javascript
复制
$order = [
    'order_no' => $orderNo,
    'amount'   => '39.90',
    'title'    => '外卖订单'
];

$response = http_post(
    'https://api.example.com/pay/create',
    $order
);

$result = json_decode($response, true);

if ($result['code'] === 200) {

    $payUrl = $result['data']['pay_url'];

    echo json_encode([
        'code' => 200,
        'pay_url' => $payUrl
    ]);

}

前端获取支付地址之后,可以引导用户完成支付。

例如:

代码语言:javascript
复制
async function payOrder(orderNo) {

    const result = await fetch('/api/order/pay', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            order_no: orderNo
        })
    });

    const data = await result.json();

    if (data.code === 200) {
        window.location.href = data.pay_url;
    }
}

但是,支付成功以后,不能仅仅根据前端页面显示来判断订单是否支付成功。

真正可靠的方式是等待支付平台的服务器回调。

六、支付回调如何处理?

例如:

代码语言:javascript
复制
public function payNotify()
{
    $data = $_POST;

    if (!$this->verifySign($data)) {
        return 'SIGN_ERROR';
    }

    $orderNo = $data['order_no'];
    $status  = $data['status'];

    if ($status !== 'SUCCESS') {
        return 'FAIL';
    }

    $order = $this->findOrder($orderNo);

    if (!$order) {
        return 'ORDER_NOT_FOUND';
    }

    if ($order['pay_status'] == 1) {
        return 'SUCCESS';
    }

    $this->updateOrder($orderNo, [
        'pay_status' => 1,
        'order_status' => 1
    ]);

    return 'SUCCESS';
}

这里需要特别注意“幂等处理”。

因为第三方平台可能重复发送支付回调。

例如:

代码语言:javascript
复制
第一次回调 → SUCCESS
第二次回调 → SUCCESS
第三次回调 → SUCCESS

如果没有幂等机制,就可能造成:

代码语言:javascript
复制
重复发货
重复增加余额
重复创建配送订单
重复发送通知

因此应该先判断订单当前状态。

代码语言:javascript
复制
if ($order['pay_status'] == 1) {
    return 'SUCCESS';
}

已经处理过的订单直接返回成功即可。

七、支付成功后如何创建配送订单?

当订单完成支付,并且商家确认接单以后,就可以进入配送流程。

系统可以调用第三方配送接口:

代码语言:javascript
复制
$deliveryData = [
    'order_no' => $order['order_no'],
    'shop' => [
        'name' => '示例餐厅',
        'address' => '示例商家地址',
        'phone' => '13800000000'
    ],
    'customer' => [
        'name' => '用户',
        'address' => '用户收货地址',
        'phone' => '13900000000'
    ],
    'goods' => [
        [
            'name' => '套餐',
            'quantity' => 1
        ]
    ]
];

$response = http_post(
    'https://api.example.com/delivery/order/create',
    $deliveryData
);

$result = json_decode($response, true);

第三方返回成功以后:

代码语言:javascript
复制
if ($result['code'] === 200) {

    $thirdOrderNo = $result['data']['order_no'];

    updateOrder($order['order_no'], [
        'third_order_no' => $thirdOrderNo,
        'delivery_status' => 1
    ]);
}

这样本地系统就可以保存第三方配送订单编号。

八、为什么一定要保存第三方订单号?

这是外卖系统对接三方接口非常重要的一点。

本地系统一般有:

代码语言:javascript
复制
order_no

第三方配送系统一般有:

代码语言:javascript
复制
third_order_no

两个订单号不能混为一谈。

建议数据库设计成:

代码语言:javascript
复制
本地订单号
第三方订单号
第三方服务商
第三方订单状态

例如:

代码语言:javascript
复制
ALTER TABLE `orders`
ADD COLUMN `third_provider` VARCHAR(50) DEFAULT NULL,
ADD COLUMN `third_order_no` VARCHAR(128) DEFAULT NULL,
ADD COLUMN `third_status` VARCHAR(50) DEFAULT NULL;

这样以后更换配送服务商时,也不会影响本地订单结构。

九、配送状态如何同步?

配送订单创建以后,第三方平台可能不断产生状态变化。

例如:

代码语言:javascript
复制
待接单
↓
骑手已接单
↓
骑手到店
↓
骑手取货
↓
配送中
↓
已送达

第三方可以通过Webhook向你的服务器发送通知。

例如:

代码语言:javascript
复制
public function deliveryNotify()
{
    $data = json_decode(
        file_get_contents('php://input'),
        true
    );

    if (!$this->verifySign($data)) {
        return 'SIGN_ERROR';
    }

    $thirdOrderNo = $data['order_no'];
    $status = $data['status'];

    $order = findOrderByThirdNo($thirdOrderNo);

    if (!$order) {
        return 'ORDER_NOT_FOUND';
    }

    updateOrder($order['order_no'], [
        'third_status' => $status,
        'delivery_status' => mapDeliveryStatus($status)
    ]);

    return 'SUCCESS';
}

其中:

代码语言:javascript
复制
function mapDeliveryStatus($status)
{
    $map = [
        'WAITING' => 1,
        'ACCEPTED' => 2,
        'DELIVERING' => 3,
        'COMPLETED' => 4
    ];

    return $map[$status] ?? 0;
}

这样第三方系统的状态就可以转换成自己系统能够理解的状态。

十、为什么不能直接使用第三方状态?

不同平台的状态定义可能完全不同。

例如:

代码语言:javascript
复制
平台A:

10 = 待接单
20 = 已接单
30 = 配送中
40 = 已完成

平台B可能是:

代码语言:javascript
复制
WAITING
ACCEPTED
DELIVERING
COMPLETED

如果业务代码直接使用第三方状态,后期更换第三方服务时,就需要修改大量业务代码。

更合理的方式是建立统一状态层。

代码语言:javascript
复制
第三方状态
      ↓
状态转换层
      ↓
系统统一状态
      ↓
用户端 / 商家端 / 管理后台

例如:

代码语言:javascript
复制
function normalizeDeliveryStatus($provider, $status)
{
    $maps = [

        'provider_a' => [
            '10' => 'WAITING',
            '20' => 'ACCEPTED',
            '30' => 'DELIVERING',
            '40' => 'COMPLETED'
        ],

        'provider_b' => [
            'WAITING' => 'WAITING',
            'ACCEPTED' => 'ACCEPTED',
            'DELIVERING' => 'DELIVERING',
            'COMPLETED' => 'COMPLETED'
        ]
    ];

    return $maps[$provider][$status] ?? 'UNKNOWN';
}

这样系统内部只需要处理统一状态。

十一、建议采用接口适配器模式

如果外卖系统未来可能接入多个第三方服务,可以进一步使用接口适配器。

例如:

代码语言:javascript
复制
interface DeliveryProvider
{
    public function createOrder($order);

    public function cancelOrder($order);

    public function queryOrder($order);

    public function calculateFee($order);
}

然后不同配送服务分别实现:

代码语言:javascript
复制
class ProviderA implements DeliveryProvider
{
    public function createOrder($order)
    {
        // 调用服务商A接口
    }

    public function cancelOrder($order)
    {
        // 调用服务商A取消接口
    }

    public function queryOrder($order)
    {
        // 查询服务商A订单
    }

    public function calculateFee($order)
    {
        // 服务商A配送费计算
    }
}

另一个服务商:

代码语言:javascript
复制
class ProviderB implements DeliveryProvider
{
    public function createOrder($order)
    {
        // 调用服务商B接口
    }

    public function cancelOrder($order)
    {
        // 调用服务商B接口
    }

    public function queryOrder($order)
    {
        // 查询服务商B订单
    }

    public function calculateFee($order)
    {
        // 服务商B配送费计算
    }
}

业务层不需要关心具体服务商。

代码语言:javascript
复制
$provider = DeliveryFactory::make($providerName);

$result = $provider->createOrder($order);

这种方式非常适合需要对接多个第三方配送服务的外卖系统。

十二、第三方接口一定要做好签名验证

第三方接口一般会涉及订单、金额、用户、地址等业务数据。

因此接口不能简单地接收请求。

可以通过:

代码语言:javascript
复制
时间戳
+
随机数
+
业务参数
+
密钥

生成签名。

例如:

代码语言:javascript
复制
$params = [
    'order_no' => $orderNo,
    'timestamp' => time()
];

ksort($params);

$string = http_build_query($params);

$sign = hash_hmac(
    'sha256',
    $string,
    $secret
);

请求时:

代码语言:javascript
复制
order_no
timestamp
sign

服务端收到请求以后重新计算签名。

代码语言:javascript
复制
$serverSign = hash_hmac(
    'sha256',
    $string,
    $secret
);

if (!hash_equals($serverSign, $requestSign)) {
    return 'SIGN_ERROR';
}

这样可以降低接口被伪造调用的风险。

十三、第三方接口调用还需要考虑超时和重试

现实环境中,第三方接口并不是百分之百稳定。

可能出现:

代码语言:javascript
复制
网络超时
接口响应慢
服务器异常
返回格式错误
服务商维护

因此不能简单写成:

代码语言:javascript
复制
$result = http_post($url, $data);

然后失败以后直接提示用户。

可以增加超时时间和重试机制。

例如:

代码语言:javascript
复制
function requestWithRetry($url, $data, $maxRetry = 3)
{
    for ($i = 0; $i < $maxRetry; $i++) {

        try {

            $result = http_post($url, $data);

            if ($result) {
                return $result;
            }

        } catch (Exception $e) {

            if ($i === $maxRetry - 1) {
                throw $e;
            }

            sleep(1);
        }
    }

    return false;
}

但是需要注意:

“重试”并不是所有接口都适合。

例如创建配送订单这种操作,如果第一次请求已经成功,只是响应没有返回,再次创建可能造成重复订单。

因此最好结合业务幂等号使用。

十四、使用幂等号避免重复创建订单

例如:

代码语言:javascript
复制
$idempotentKey = hash(
    'sha256',
    $order['order_no'] . '_delivery'
);

提交第三方接口时:

代码语言:javascript
复制
$deliveryData['request_id'] = $idempotentKey;

第三方服务可以根据这个唯一请求号判断:

代码语言:javascript
复制
第一次请求:
request_id = ABC123

第二次请求:
request_id = ABC123

如果已经创建过,就直接返回原来的配送订单。

如果第三方接口支持幂等机制,这种设计应该优先使用。

十五、建议增加第三方接口日志

外卖系统在实际运营过程中,接口问题是比较难排查的一类问题。

例如:

代码语言:javascript
复制
用户说已经支付
商家说没有收到订单

这时候后台需要知道:

代码语言:javascript
复制
什么时候调用了支付接口?
发送了什么参数?
第三方返回了什么?
有没有收到回调?
回调验证是否成功?
订单最终修改成了什么状态?

因此建议建立接口日志表。

代码语言:javascript
复制
CREATE TABLE `api_logs` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `provider` VARCHAR(50) NOT NULL,
    `api_name` VARCHAR(100) NOT NULL,
    `request_id` VARCHAR(100) DEFAULT NULL,
    `request_data` TEXT,
    `response_data` TEXT,
    `status` VARCHAR(30) DEFAULT NULL,
    `created_at` DATETIME NOT NULL,
    PRIMARY KEY (`id`)
);

记录:

代码语言:javascript
复制
接口名称
请求时间
请求参数
响应结果
订单号
第三方订单号
请求ID
错误信息

出现问题时,管理员就可以直接查看接口日志。

十六、前端不要直接调用敏感的第三方接口

这是外卖系统开发过程中非常重要的一点。

不推荐:

代码语言:javascript
复制
fetch('https://third-party.com/api/order', {
    method: 'POST',
    body: JSON.stringify(order)
});

因为这样可能暴露:

代码语言:javascript
复制
API Key
Secret
业务参数
第三方接口地址

正确的方式应该是:

代码语言:javascript
复制
前端
 ↓
自己的业务API
 ↓
业务服务器
 ↓
第三方接口

例如:

代码语言:javascript
复制
fetch('/api/order/create', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        shop_id: 1001,
        goods: cart
    })
});

然后由PHP后端完成第三方接口调用。

这样能够更好地保护第三方接口密钥。

十七、三方接口对接建议采用统一服务层

随着系统功能越来越多,可以将第三方服务统一管理。

例如:

代码语言:javascript
复制
Service
│
├── Payment
│   ├── ProviderA
│   └── ProviderB
│
├── Delivery
│   ├── ProviderA
│   └── ProviderB
│
├── Map
│   ├── ProviderA
│   └── ProviderB
│
└── Sms
    ├── ProviderA
    └── ProviderB

业务代码只需要调用:

代码语言:javascript
复制
$paymentService->create($order);

$deliveryService->create($order);

$mapService->geocode($address);

$smsService->send($phone, $message);

而不需要在订单业务代码里面直接写大量第三方接口请求。

这种架构更加方便维护和扩展。

十八、外卖系统对接三方的整体架构

综合来看,可以设计成:

代码语言:javascript
复制
                  用户端
             H5 / 小程序 / APP
                     │
                     ▼
               API业务层
                     │
        ┌────────────┼────────────┐
        │            │            │
        ▼            ▼            ▼
     订单服务      支付服务      配送服务
        │            │            │
        │            ▼            ▼
        │         第三方支付    第三方配送
        │
        ├──────────────┐
        │              │
        ▼              ▼
      MySQL          Redis
        │
        ▼
     接口日志
        │
        ▼
     管理后台

通过这样的架构,可以把自己的核心业务和第三方服务进行隔离。

十九、外卖系统对接三方时需要重点关注哪些问题?

在实际开发过程中,建议重点考虑以下几个方面。

1. 接口稳定性

第三方服务出现异常时,自己的订单系统不能跟着直接瘫痪。

2. 数据一致性

支付成功、订单状态、配送状态等数据需要保持一致。

3. 幂等处理

支付回调、配送回调、订单创建等接口都应该考虑重复请求。

4. 签名验证

所有涉及订单、支付、用户等数据的接口,都应该进行身份认证和签名校验。

5. 接口日志

建议完整记录请求、响应、订单号和错误信息。

6. 超时机制

第三方接口不能无限等待,需要设置合理的请求超时时间。

7. 重试机制

对于可安全重试的接口,可以增加自动重试。

8. 状态转换

不要让第三方状态直接渗透到核心业务系统。

9. 服务商切换

如果未来可能更换第三方服务,应提前设计适配器层。

10. 敏感信息保护

API Key、Secret、支付密钥等信息不能直接写入前端代码。

外卖系统
外卖系统

二十、总结

外卖系统对接三方接口,本质上是解决不同系统之间的数据通信和业务协同问题。

其中订单、支付和配送属于最核心的三个环节。

比较合理的技术方案应该是:

代码语言:javascript
复制
统一订单中心
+
支付服务层
+
配送服务层
+
第三方接口适配器
+
Webhook回调
+
幂等机制
+
签名验证
+
接口日志
+
异常重试

通过这种方式,可以让外卖系统具备更好的扩展能力。

当业务需要接入新的支付服务、配送服务或其他第三方系统时,只需要增加对应的接口适配器,而不需要大规模修改原有订单业务。

对于企业而言,如果后期还需要增加校园外卖、同城配送、商家自配送、跑腿配送等业务,也可以在统一订单中心的基础上继续扩展。

因此,在进行外卖系统定制开发时,与其只考虑“能不能对接三方”,更应该从系统架构层面考虑“如何让三方接口稳定、安全、可扩展地接入自己的业务系统”。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、外卖系统为什么需要对接三方接口?
  • 二、外卖系统对接三方接口的核心流程
  • 三、订单系统如何设计?
  • 四、订单号设计
  • 五、支付接口如何对接?
  • 六、支付回调如何处理?
  • 七、支付成功后如何创建配送订单?
  • 八、为什么一定要保存第三方订单号?
  • 九、配送状态如何同步?
  • 十、为什么不能直接使用第三方状态?
  • 十一、建议采用接口适配器模式
  • 十二、第三方接口一定要做好签名验证
  • 十三、第三方接口调用还需要考虑超时和重试
  • 十四、使用幂等号避免重复创建订单
  • 十五、建议增加第三方接口日志
  • 十六、前端不要直接调用敏感的第三方接口
  • 十七、三方接口对接建议采用统一服务层
  • 十八、外卖系统对接三方的整体架构
  • 十九、外卖系统对接三方时需要重点关注哪些问题?
    • 1. 接口稳定性
    • 2. 数据一致性
    • 3. 幂等处理
    • 4. 签名验证
    • 5. 接口日志
    • 6. 超时机制
    • 7. 重试机制
    • 8. 状态转换
    • 9. 服务商切换
    • 10. 敏感信息保护
  • 二十、总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档