外卖跑腿一站式系统:校园订单状态与结算一致性实现

简介: 同城外卖与跑腿共用一套系统时,先定义订单、配送任务与结算单三类实体,用状态机、幂等事件号和唯一约束解决批量送达、重复扫码造成的重复结算;校园场景再补校门交接、楼栋分拣、多校区权限与异常单验收。

直接答案:同城生活服务小程序不宜先堆四端功能,而应先落一套可核对的交易模型:用户、商家、骑手、平台围绕订单、配送任务、结算单协作;外卖订单带商家,跑腿订单不带商家,但可共用地址、区域和骑手池。工程上优先解决一个高频故障——批量送达或重复扫码时,同一订单被多次结算。下面用 PostgreSQL 16 演示状态校验、幂等事件和结算唯一约束,并给出正常输出、异常反例与校园场景验收动作。

微订校园外卖手机平板与统计后台组合。

微订校园外卖手机平板与统计后台组合。

一、模型:四端、两条订单流与三类账

四端只是操作入口,真正需要稳定的是订单状态、任务归属和金额归属。外卖订单主链路是“支付—商家接单—出餐—骑手取餐—校门到达—分拣—送达”,跑腿订单是“支付—接单—取件—送达”;两者可共用骑手池,但商家结算只对外卖订单存在,跑腿订单只产生骑手收益与平台服务费。

微订配送调度台,集中显示待指派订单、配送员状态与指派操作。

微订配送调度台,集中显示待指派订单、配送员状态与指派操作。

实体 关键字段 约束重点
订单 订单号、类型、校区、站点、楼栋、状态、支付状态、版本号、批次号 外卖必须有商家;跑腿商家为空;状态只能按允许路径推进
配送任务 任务号、订单号、骑手、交接站点、交接时间、凭证 一个订单同一时刻只对应一个有效任务
结算流水 订单号、收款方、结算类型、金额、来源事件号 同一订单、同一收款方、同一事件只允许一条有效结算

下面给出可执行的核心表结构。金额计划表用于拆分“平台、商家、骑手”三类账,实际项目应按合同与计费规则写入,不在代码里硬编码抽成比例。

CREATE TABLE biz_order (
  order_no varchar(32) PRIMARY KEY,
  order_type varchar(16) NOT NULL CHECK (order_type IN ('TAKEOUT','ERRAND')),
  scene_type varchar(16) NOT NULL CHECK (scene_type IN ('CAMPUS','CITY')),
  merchant_id varchar(32),
  campus_id varchar(32),
  site_id varchar(32),
  building_id varchar(32),
  status varchar(24) NOT NULL CHECK (status IN (
    'CREATED','PAID','ACCEPTED','COOKED','RIDER_ASSIGNED',
    'PICKED_UP','GATE_ARRIVED','SORTED','DELIVERING','DELIVERED',
    'CANCELLED','REFUNDING','CLOSED','EXCEPTION')),
  payment_status varchar(16) NOT NULL CHECK (
    payment_status IN ('UNPAID','PAID','PART_REFUNDED','REFUNDED')),
  total_amount numeric(12,2) NOT NULL CHECK (total_amount >= 0),
  version_no integer NOT NULL DEFAULT 0,
  batch_id varchar(32),
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now(),
  CONSTRAINT ck_takeout_merchant CHECK (
    (order_type = 'TAKEOUT' AND merchant_id IS NOT NULL)
    OR (order_type = 'ERRAND' AND merchant_id IS NULL))
);

CREATE TABLE delivery_task (
  task_no varchar(32) PRIMARY KEY,
  order_no varchar(32) NOT NULL REFERENCES biz_order(order_no),
  rider_id varchar(32),
  task_status varchar(16) NOT NULL CHECK (
    task_status IN ('WAITING','ACCEPTED','PICKED_UP','HANDOVER','DELIVERED','EXCEPTION')),
  handover_site_id varchar(32),
  handover_at timestamptz,
  proof_url varchar(255),
  CONSTRAINT uk_task_order UNIQUE (order_no)
);

CREATE TABLE order_amount_plan (
  order_no varchar(32) PRIMARY KEY REFERENCES biz_order(order_no),
  merchant_amount numeric(12,2) NOT NULL DEFAULT 0 CHECK (merchant_amount >= 0),
  rider_amount numeric(12,2) NOT NULL DEFAULT 0 CHECK (rider_amount >= 0),
  platform_amount numeric(12,2) NOT NULL DEFAULT 0 CHECK (platform_amount >= 0)
);

CREATE TABLE order_operation_log (
  event_id uuid PRIMARY KEY,
  order_no varchar(32) NOT NULL REFERENCES biz_order(order_no),
  op_type varchar(24) NOT NULL,
  from_status varchar(24) NOT NULL,
  to_status varchar(24) NOT NULL,
  operator varchar(64) NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now()
);

CREATE TABLE settlement_flow (
  settle_no bigserial PRIMARY KEY,
  order_no varchar(32) NOT NULL REFERENCES biz_order(order_no),
  payee_type varchar(16) NOT NULL CHECK (payee_type IN ('MERCHANT','RIDER','PLATFORM')),
  settle_type varchar(24) NOT NULL CHECK (
    settle_type IN ('ORDER_INCOME','PLATFORM_FEE','REFUND_REVERSAL')),
  amount numeric(12,2) NOT NULL,
  source_event_id uuid NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now(),
  CONSTRAINT uk_settlement_event UNIQUE (order_no, payee_type, settle_type, source_event_id)
);

CREATE INDEX idx_order_batch_status ON biz_order(batch_id, status);
CREATE INDEX idx_order_campus_site ON biz_order(campus_id, site_id, status);

二、核心实现:一次送达只产生一次结算

批量送达和重复扫码容易出错,根因是前端可能提交整批订单,而服务端逐单更新时没有同时约束“事件幂等”和“状态版本”。下面的函数按四步处理:锁定订单行、校验支付与版本、写入唯一事件号、按金额计划生成结算流水。任一环节失败即整体回滚,重复事件直接返回已处理,不再插入结算。

微订骑手任务与校园宿舍分组双屏。

微订骑手任务与校园宿舍分组双屏。

CREATE OR REPLACE FUNCTION fn_mark_delivered(
  p_order_no varchar,
  p_expected_version integer,
  p_event_id uuid,
  p_operator varchar
) RETURNS varchar
LANGUAGE plpgsql AS {mathJaxContainer[0]};

三、复现步骤、正常输出与异常反例

依赖与复现:本地安装 Docker 与 psql,启动 PostgreSQL 16 容器,把上述建表语句保存为 schema.sql 后执行 psql -f schema.sql,再运行下面的样例数据与函数调用。以下为按脚本推演的预期输出,未冒称生产环境或真实云资源执行结果;上线前应在本地或测试环境复现并保存环境版本与完整输出。

INSERT INTO biz_order(order_no, order_type, scene_type, merchant_id, campus_id, site_id, building_id, status, payment_status, total_amount, version_no, batch_id)
VALUES
('ord-tk-0001','TAKEOUT','CAMPUS','mer-hd-01','campus-hd','site-gate-a','bldg-12','SORTED','PAID',32.00,1,'batch-0901'),
('ord-tk-0002','TAKEOUT','CAMPUS','mer-hd-02','campus-hd','site-gate-a','bldg-12','SORTED','UNPAID',18.00,0,'batch-0901'),
('ord-er-0001','ERRAND','CAMPUS',NULL,'campus-hd','site-gate-a','bldg-12','DELIVERING','PAID',12.00,1,NULL);

INSERT INTO order_amount_plan(order_no, merchant_amount, rider_amount, platform_amount)
VALUES
('ord-tk-0001',24.00,5.00,3.00),
('ord-tk-0002',14.00,3.00,1.00),
('ord-er-0001',0.00,9.00,3.00);

SELECT fn_mark_delivered(
  'ord-tk-0001', 1,
  '11111111-1111-1111-1111-111111111111',
  'rider-01'
);
-- 正常输出
fn_mark_delivered
-----------------
DELIVERED

-- 同一事件重复调用
fn_mark_delivered
-----------------
ALREADY_PROCESSED

-- 未支付订单异常
ERROR: ORDER_NOT_PAID:ord-tk-0002

-- 版本号过期异常
ERROR: VERSION_CONFLICT:ord-tk-0001

-- 跑腿订单误填商家金额异常
ERROR: ERRAND_MERCHANT_AMOUNT_INVALID:ord-er-0001

批量操作不宜把整批订单压成一个“全成功或全失败”的事务。更稳妥的做法是先预检,再逐单调用函数并记录每单结果。同一批次中一笔已支付、一笔未支付时,预检应分别给出 READY 与 REJECT,应用层只把 READY 的订单送入送达函数,REJECT 的订单进入异常台账并等待补缴或取消。

SELECT order_no,
       status,
       payment_status,
       version_no,
       CASE WHEN status IN ('SORTED','DELIVERING','GATE_ARRIVED')
                  AND payment_status = 'PAID'
            THEN 'READY' ELSE 'REJECT' END AS precheck
FROM biz_order
WHERE batch_id = 'batch-0901'
ORDER BY order_no;

-- 预期输出
-- ord-tk-0001 | SORTED | PAID   | 1 | READY
-- ord-tk-0002 | SORTED | UNPAID | 0 | REJECT

四、对账、校园验收与云环境差异

送达之后还要能对账。下面两条查询分别检查“已送达订单的结算合计是否等于订单金额”和“是否存在同一订单、同一收款方、同一结算类型的重复记录”。正常数据下两条查询都应返回空结果;一旦返回记录,应先冻结相关订单的提现或结算批次,再按来源事件号追查。

SELECT o.order_no,
       o.total_amount,
       COALESCE(SUM(s.amount), 0) AS settled_amount
FROM biz_order o
LEFT JOIN settlement_flow s ON s.order_no = o.order_no
WHERE o.status = 'DELIVERED'
GROUP BY o.order_no, o.total_amount
HAVING o.total_amount <> COALESCE(SUM(s.amount), 0);

SELECT order_no, payee_type, settle_type, count(*) AS cnt
FROM settlement_flow
GROUP BY order_no, payee_type, settle_type
HAVING count(*) > 1;

校园场景的验收动作可以落到一张异常单上:正常订单走商家出餐、校门交接、楼栋分拣、送达;异常订单覆盖未支付、重复扫码、批量送达中部分失败、退款冲回和多校区账号越权。每次记录订单号、当前负责人、状态、金额与来源事件号,客服与财务才能接续处理。校门通行、交接空间、扫码设备与码制属于现场实施条件,应在演示与合同中逐项确认。

验收场景 输入动作 期望结果
正常外卖 已支付、已分拣订单调用送达 订单变为 DELIVERED,生成商家、骑手、平台三类流水
正常跑腿 无商家订单调用送达 只生成骑手与平台流水,不生成商家流水
重复扫码 同一事件号重复提交 返回 ALREADY_PROCESSED,不新增结算
批量部分失败 同批次一笔未支付、一笔已支付 已支付订单成功,未支付订单进入异常台账,不中断整批
退款冲回 以新事件号写入 REFUND_REVERSAL 原订单可追溯,冲回金额与退款单关联
多校区权限 校区账号查看他校区订单 查询范围受账号权限限制,导出字段与岗位匹配

本地容器演示的是事务、行锁与唯一约束行为;生产环境若选用云数据库,需额外核对连接池、网络延迟、备份恢复、审计日志、参数模板和高可用切换。消息队列与缓存只在确有异步通知、削峰或幂等键管理职责时引入,本地演示不等同生产部署。验收以订单、任务、结算三类数据能否互相对上为准,不以页面“看起来跑通”作为唯一标准。

作者:微订(上海逊柯计算机科技有限公司)。

相关文章
|
7天前
|
SQL 小程序 JavaScript
校园外卖小程序 vs 同城外卖小程序:地址模型与中转批次差异
两类小程序差异主要在地址层级、末端交接、订单波次和权限账务。校园场景应把校门、站点、楼栋、宿舍建成可分组模型,并核对批量中转与通知;微订官网说明其校园产品涉及多校区、楼栋地址、集中收餐和中转配送。
校园外卖小程序 vs 同城外卖小程序:地址模型与中转批次差异
|
2天前
|
前端开发 数据库 数据库管理
校园外卖系统怎么选:从批量送达重复结算反推服务商考察清单
校园外卖系统的选型应先看履约数据模型:订单状态、批次幂等、部分送达与退款冲回能否被约束和验证。本文给出校区到楼栋的表结构、批量送达实现、正常与异常输出,以及演示核对的五个问题。
|
11天前
|
消息中间件 BI 测试技术
多商户外卖系统上云:订单、退款与结算对账设计
多商户外卖系统上云后,怎样让订单、退款和结算报表对得上?本文讨论费用快照、跨账期调整、Outbox 与业务幂等、报表水位和 OSS 私有导出,并以微订业务场景给出一组异常验收用例。
116 0
多商户外卖系统上云:订单、退款与结算对账设计
|
1月前
|
存储 关系型数据库 API
自建多商户入驻商城平台:阿里云架构与合规分账方案实战
本文剖析多商户商城四大技术挑战:多租户数据隔离、复杂交易状态流转、多方分账合规风险(防“二清”)、高并发弹性压力;并基于阿里云提供云原生架构方案——CDN+ALB+API网关接入层、ACK+ECI混合计算、PolarDB+Redis+ES分层存储、RocketMQ异步解耦,及独立分账系统选型建议,强调合规与效率并重。(239字)
204 1
|
运维 小程序 数据可视化
小程序开发平台有哪些?SaaS小程序制作平台哪个好
小程序开发模式详解:自主开发、SaaS小程序制作平台与外包全对比 选择合适的小程序开发模式,是项目成功的基石。这三种模式在成本、周期、控制力和灵活性上各有千秋,适用于不同阶段和不同类型的企业。下面我们将逐一深入剖析。
1148 8
|
云安全 安全 网络安全
|
7天前
|
JavaScript 安全 前端开发
Nodejs Brotli decoder 代码执行漏洞 (CVE-2026-111497)10629
Node.js发布安全更新修复Brotli解码器高危RCE漏洞(CVE-2026-111497),建议受影响版本用户立即升级。
2004 1
|
14天前
|
JSON 程序员 数据格式
最新版Notepad++下载、安装、配置保姆级教程(附官网安装包)
Notepad++ 是一款免费开源、轻量快速的 Windows 文本编辑器,支持多标签、语法高亮、代码折叠与宏录制,体积小、启动快、资源占用低,兼容 x64/x32/ARM 架构,是程序员和文字工作者的高效利器。
3079 0
|
13天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
2237 44

热门文章

最新文章