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

简介: 本文详解外卖系统对接支付、配送、地图等三方接口的完整方案,涵盖架构设计、订单/支付/配送状态同步、幂等与签名验证、日志记录及适配器模式等核心实践,助力构建稳定、安全、可扩展的外卖平台。(239字)

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

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

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

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

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

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

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

例如:

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

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

例如:

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

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

可以采用下面这种结构:

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

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

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

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

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

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

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

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

三、订单系统如何设计?

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

例如MySQL可以设计为:

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`)
);

其中:

pay_status
0 = 未支付
1 = 已支付
2 = 已退款

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

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

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

例如:

商家已接单
骑手已接单
骑手已到店
骑手取餐
骑手送达
用户确认收货

四、订单号设计

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

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

function createOrderNo()
{
   
    return date('YmdHis') . mt_rand(100000, 999999);
}

$orderNo = createOrderNo();

echo $orderNo;

生成结果类似:

20260901172535123456

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

可以使用:

业务订单号
+
数据库ID
+
第三方订单号

分别管理。

例如:

本地订单号:20260901172535123456

数据库ID:10235

第三方订单号:THIRD202609010001

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

五、支付接口如何对接?

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

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

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

例如后端创建支付订单:

$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
    ]);

}

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

例如:

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;
    }
}

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

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

六、支付回调如何处理?

例如:

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';
}

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

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

例如:

第一次回调 → SUCCESS
第二次回调 → SUCCESS
第三次回调 → SUCCESS

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

重复发货
重复增加余额
重复创建配送订单
重复发送通知

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

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

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

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

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

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

$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);

第三方返回成功以后:

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

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

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

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

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

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

本地系统一般有:

order_no

第三方配送系统一般有:

third_order_no

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

建议数据库设计成:

本地订单号
第三方订单号
第三方服务商
第三方订单状态

例如:

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;

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

九、配送状态如何同步?

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

例如:

待接单
↓
骑手已接单
↓
骑手到店
↓
骑手取货
↓
配送中
↓
已送达

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

例如:

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';
}

其中:

function mapDeliveryStatus($status)
{
   
    $map = [
        'WAITING' => 1,
        'ACCEPTED' => 2,
        'DELIVERING' => 3,
        'COMPLETED' => 4
    ];

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

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

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

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

例如:

平台A:

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

平台B可能是:

WAITING
ACCEPTED
DELIVERING
COMPLETED

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

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

第三方状态
      ↓
状态转换层
      ↓
系统统一状态
      ↓
用户端 / 商家端 / 管理后台

例如:

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';
}

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

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

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

例如:

interface DeliveryProvider
{
   
    public function createOrder($order);

    public function cancelOrder($order);

    public function queryOrder($order);

    public function calculateFee($order);
}

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

class ProviderA implements DeliveryProvider
{
   
    public function createOrder($order)
    {
   
        // 调用服务商A接口
    }

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

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

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

另一个服务商:

class ProviderB implements DeliveryProvider
{
   
    public function createOrder($order)
    {
   
        // 调用服务商B接口
    }

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

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

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

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

$provider = DeliveryFactory::make($providerName);

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

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

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

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

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

可以通过:

时间戳
+
随机数
+
业务参数
+
密钥

生成签名。

例如:

$params = [
    'order_no' => $orderNo,
    'timestamp' => time()
];

ksort($params);

$string = http_build_query($params);

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

请求时:

order_no
timestamp
sign

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

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

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

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

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

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

可能出现:

网络超时
接口响应慢
服务器异常
返回格式错误
服务商维护

因此不能简单写成:

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

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

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

例如:

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;
}

但是需要注意:

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

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

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

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

例如:

$idempotentKey = hash(
    'sha256',
    $order['order_no'] . '_delivery'
);

提交第三方接口时:

$deliveryData['request_id'] = $idempotentKey;

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

第一次请求:
request_id = ABC123

第二次请求:
request_id = ABC123

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

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

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

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

例如:

用户说已经支付
商家说没有收到订单

这时候后台需要知道:

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

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

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`)
);

记录:

接口名称
请求时间
请求参数
响应结果
订单号
第三方订单号
请求ID
错误信息

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

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

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

不推荐:

fetch('https://third-party.com/api/order', {
   
    method: 'POST',
    body: JSON.stringify(order)
});

因为这样可能暴露:

API Key
Secret
业务参数
第三方接口地址

正确的方式应该是:

前端
 ↓
自己的业务API
 ↓
业务服务器
 ↓
第三方接口

例如:

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

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

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

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

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

例如:

Service
│
├── Payment
│   ├── ProviderA
│   └── ProviderB
│
├── Delivery
│   ├── ProviderA
│   └── ProviderB
│
├── Map
│   ├── ProviderA
│   └── ProviderB
│
└── Sms
    ├── ProviderA
    └── ProviderB

业务代码只需要调用:

$paymentService->create($order);

$deliveryService->create($order);

$mapService->geocode($address);

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

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

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

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

综合来看,可以设计成:

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

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

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

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

1. 接口稳定性

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

2. 数据一致性

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

3. 幂等处理

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

4. 签名验证

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

5. 接口日志

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

6. 超时机制

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

7. 重试机制

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

8. 状态转换

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

9. 服务商切换

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

10. 敏感信息保护

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

外卖系统.png

二十、总结

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

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

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

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

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

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

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

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

相关文章
|
2月前
|
人工智能 自然语言处理 数据挖掘
虚拟数字人直播系统搭建,打造企业智能化直播营销新模式
虚拟数字人直播系统融合AI大模型、语音合成、计算机视觉等技术,实现AI数字人自动直播、智能讲解、实时互动、脚本生成与数据分析,支持全天候、多场景智能营销,显著降本增效,推动直播从“人工驱动”迈向“AI智能运营”。
|
自然语言处理 Ubuntu 安全
14个你必须要知道的ubuntu快捷键
14个你必须要知道的ubuntu快捷键
14个你必须要知道的ubuntu快捷键
|
1月前
|
人工智能 自然语言处理
【学生必看】免费领价值 59 元的 1 个月 Qoder CN 专业版
Qoder是阿里推出的国产智能体工作台,像一位全能AI助手:听懂你的自然语言指令,自动做PPT、写文档、查资料、处理表格、操作网页等。高校学生认证后,可0元领取1个月专业版(含Qwen3.8等多模型),限时福利!
1683 3
【学生必看】免费领价值 59 元的 1 个月 Qoder CN 专业版
|
1月前
|
网络协议 Java 测试技术
网站开发性能测试-JMeter 压测 Tomcat 接口的并发吞吐量(QPS)
在企业级网站开发、Java Web 系统上线与后端架构调优实战中,**性能压力测试(Stress / Load Testing)** 是验证系统高可用性、探测吞吐量极限与排除潜在并发死锁、内存泄露(Memory Leak)的必经之路。 对于采用 Apache Tomcat 作为 Servlet 容器的企业级应用(如 Spring Boot 内嵌 Tomcat 或独立 Tomcat 9/10 集群),单台服务器在面对每秒数百乃至数万次的高频请求时,其底层线程池调度、I/O 多路复用模型(NIO/NIO2)、JVM 垃圾回收机制以及操作系统内核 TCP 队列均会承受极限考验。
|
1月前
|
数据采集 人工智能 监控
从功能对比到价值匹配:2026数据治理系统工具选型逻辑重构
2026年数据治理工具选型已从“功能清单对比”转向“技术路线匹配”。AI原生能力、全链路自动化与业务适配度成新三角标准。本文深度解析阿里云Dataphin、Informatica IDMC等五款主流工具,提出四步价值导向选型框架,助力企业精准匹配自身数据阶段与AI战略。(239字)
|
3月前
|
SQL 分布式计算 对象存储
Lake Search:ES x Paimon 让湖上多模态数据可搜可用
当图片、视频、文本和向量在 Paimon 中增长到 PB 级,传统“同步到湖外再建索引”的方式,会让搜索面临数据就绪慢、第二份事实数据成本高和版本治理复杂等问题。本文介绍阿里云 Elasticsearch 9.4 Search Lake 如何直接挂载与 Paimon 表版本关联的 Global Index,在不复制事实数据的前提下提供 BM25、kNN、结构化过滤、排序与聚合能力,并结合多模态样本湖场景拆解方案架构、Demo 及性能与成本取舍。 关键词:Search Lake、Apache Paimon、Elasticsearch 9.4、Global Index、OpenLake、多模态检索
492 0
|
8月前
|
人工智能 API Android开发
一封律师函引发的连锁反应:OpenClaw 命名风波背后的开源生态博弈
1月底,AI工具Clawdbot因Anthropic律师函三度更名(Clawdbot→Moltbot→OpenClaw),暴露开源生态对商业API的深度依赖。更名引发账号抢注、假币炒作,凸显品牌脆弱性;商标边界之争折射大厂与开发者的权力张力——“开源”常仅限调用层,智能内核仍受制于闭源模型。
769 3
|
数据采集 测试技术 API
python爬虫之app爬取-微信朋友圈
搭建appium环境,appium基本使用,API操作等等
1120 0
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####