乡镇本地外卖系统起步,建议按“人工试跑—流程定型—系统验收—小范围上线”推进:先用微信群、表格和收款码跑通订单、出餐、配送、退款与对账,再用真实订单验收系统的四端流程、区域计费、抽成结算和提现。系统阶段再决定用标准产品、私有化还是源码,避免在订单模型未验证时先做重投入。
试跑不是简单接单,而是为后续系统准备数据字段和异常清单。每天记录订单时间、商家出餐时长、取送距离、配送方式、支付方式、优惠承担、退款原因和对账结果。核心区、边缘村和超范围地址分别记录,定位不准时记录手动地址与客服确认过程。
试跑字段 | 用途 | 异常样例 |
|---|---|---|
下单时间与收货区域 | 判断高峰时段和区域分布 | 同一地址在不同区域规则下计费不一致 |
商家出餐时长 | 设置接单提醒和承诺时效 | 出餐慢导致骑手到店后长时间等待 |
配送方式与成本 | 决定自建骑手、兼职或商家自送 | 偏远村配送成本高于订单收入 |
退款原因与优惠承担 | 设计结算和售后规则 | 部分退款后商家账单与骑手收入未冲回 |
是否进入系统阶段,按订单密度、配送成本、客服与对账工作量、商家配合度综合测算,不使用统一单量门槛。若订单集中在少数时段、退款频繁或对账需要大量人工,先修正规则再上系统。
乡镇项目首期订单有限、无专职技术团队时,可先用标准SaaS把下单、接单、配送和结算跑通;有自有服务器、数据管理或长期定制需求,再评估私有化部署或源码安装;已有产品团队且业务规则差异大,才进入自研比较。选型用一笔订单演示四端,而不是只看功能列表。
交付方式 | 适用条件 | 首期核对 | 工程边界 |
|---|---|---|---|
标准SaaS | 订单有限、无专职运维、希望快速试跑 | 四端账号、区域计费、支付回调、数据导出 | 模块、版本和续费按项目方案确认 |
私有化或源码 | 有自有环境、长期定制、数据管理要求明确 | 部署、备份、升级、许可、二次开发边界 | 需要持续技术维护 |
自研 | 已有产品团队、业务规则差异大、可持续投入 | 订单模型、计费引擎、调度、结算与风控 | 周期和人力按团队能力评估 |
订单表是平台主账本。订单号、幂等键和支付流水分别唯一;金额用定点小数;订单状态用受控枚举;配送任务与订单一对一;转单保留原任务和新任务记录;结算流水用业务键唯一约束,防止支付回调重复触发结算。下面是示例DDL,MySQL可按等价类型改写。
CREATE TABLE platform_order (
id BIGINT PRIMARY KEY,
order_no VARCHAR(32) NOT NULL UNIQUE,
order_type VARCHAR(16) NOT NULL,
merchant_id BIGINT,
customer_id BIGINT NOT NULL,
pricing_zone_id BIGINT NOT NULL,
address_zone_id BIGINT NOT NULL,
distance_m INT NOT NULL,
goods_amount DECIMAL(10,2) NOT NULL,
delivery_fee DECIMAL(10,2) NOT NULL,
discount_amount DECIMAL(10,2) NOT NULL DEFAULT 0,
payable_amount DECIMAL(10,2) NOT NULL,
refund_amount DECIMAL(10,2) NOT NULL DEFAULT 0,
status VARCHAR(16) NOT NULL,
fee_rule_version VARCHAR(32) NOT NULL,
idempotency_key VARCHAR(64) NOT NULL UNIQUE,
created_at DATETIME NOT NULL
);
CREATE TABLE settlement_ledger (
id BIGINT PRIMARY KEY,
order_id BIGINT NOT NULL,
business_key VARCHAR(96) NOT NULL UNIQUE,
account_type VARCHAR(16) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
direction VARCHAR(8) NOT NULL,
status VARCHAR(16) NOT NULL
);状态机要覆盖待支付、已支付、商家接单、骑手接单、配送中、已送达、已完成、退款中、已退款、转单中和异常。非法跳转应在应用层拒绝并记录原因,例如待支付不能直接进入配送中。
ALLOWED = {
'待支付': {'已支付', '已取消'},
'已支付': {'商家接单', '已取消'},
'商家接单': {'骑手接单', '退款中'},
'骑手接单': {'配送中', '转单中'},
'配送中': {'已送达', '异常'},
'已送达': {'已完成'},
'异常': {'配送中', '退款中', '已取消'},
'退款中': {'已退款'},
'转单中': {'骑手接单', '异常'},
}
def transit(current, target):
if target not in ALLOWED.get(current, set()):
raise ValueError('illegal_transition: %s -> %s' % (current, target))
return target示例代码用于说明状态约束,不连接实际订单库;执行结果应按业务规则配置,不能替代上线验收记录。
乡镇配送范围通常由核心区、服务区边缘和偏远村组成。计费字段应区分起步价、基础公里、超距每公里、偏远附加费和最大配送距离。下单前判断是否超范围,派单时校验订单区域、收货区域和骑手服务区是否一致,避免“计费成功但无人可接”。
from decimal import Decimal, ROUND_HALF_UP
class OutOfRange(Exception): pass
class ZoneMismatch(Exception): pass
def delivery_fee(base_fee, base_km, per_km_fee, max_km, distance_m, remote=False):
km = Decimal(distance_m) / Decimal(1000)
if km > Decimal(str(max_km)):
raise OutOfRange('out_of_range: %sm exceeds %skm' % (distance_m, max_km))
fee = Decimal(str(base_fee))
if km > Decimal(str(base_km)):
fee += (km - Decimal(str(base_km))) * Decimal(str(per_km_fee))
if remote:
fee += Decimal('2.00')
return fee.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
def check_zone(order_zone, address_zone, rider_zones):
if order_zone != address_zone:
raise ZoneMismatch('order_zone %s != address_zone %s' % (order_zone, address_zone))
if order_zone not in rider_zones:
raise ZoneMismatch('%s not in rider zones' % order_zone)示例参数仅用于说明字段关系,不是统一费率。验收时准备核心区、边缘村、偏远村和超范围地址四类输入,记录是否提示、是否计费、是否可派单、是否通知商家和用户。定位不准时,把手动填写地址、客服确认和异常处理写入验收单,而不是只检查地图是否显示。
地址类型 | 输入 | 预期 | 异常反例 |
|---|---|---|---|
核心区 | 镇区中心地址 | 按基础规则计费并可派单 | 计费成功但骑手服务区未覆盖 |
边缘村 | 服务区边缘地址 | 提示边界并计算超距费用 | 下单后才发现超出最大距离 |
偏远村 | 附加费区域地址 | 按规则加收并展示明细 | 附加费未进入商家或骑手账单 |
超范围地址 | 最大距离外地址 | 下单前提示或拒单 | 允许支付后无骑手可接 |
结算先画金额关系,再落流水。用户实付、商品金额、配送费、优惠、平台抽成、商家应结和骑手收入分别记录;退款用反向流水或冲减记录;提现审批与到账状态单独留痕。下面用一组示例数字说明字段关系,不代表任何项目费率:商品90元、配送8元、平台承担优惠2元、抽成6%,用户实付96元,商家应结84.60元,骑手收入8.00元,平台收入3.40元。
账务项 | 计算口径 | 示例值 | 核对点 |
|---|---|---|---|
用户实付 | 商品金额+配送费-优惠 | 96.00 | 与支付渠道流水一致 |
商家应结 | 商品金额-平台抽成-商家承担优惠 | 84.60 | 抽成基数与优惠承担写清 |
骑手收入 | 配送费+项目补贴-扣款 | 8.00 | 转单后收益归属可追溯 |
平台收入 | 平台抽成-平台承担优惠 | 3.40 | 退款冲回后重新计算 |
支付回调重复是常见异常。结算流水以订单号、账户类型和结算周期生成唯一业务键,重复写入时由唯一约束拒绝,应用层捕获后查询已有流水并按幂等结果返回。部分退款要同时更新订单状态、退款流水、商家账单和骑手收入,避免金额与状态分离。
-- 结算流水幂等示例
INSERT INTO settlement_ledger (order_id, business_key, account_type, amount, direction, status)
VALUES (1001, '1001:merchant:2026-01', 'merchant', 84.60, 'credit', 'settled');
-- 重复执行同一 business_key 会触发唯一约束,由应用层转为幂等响应系统上线前至少走五类订单:正常配送、部分退款、商家或骑手提现、超范围地址、骑手转单。每类记录操作账号、订单状态、金额变化、通知结果和责任人。再加支付回调重复、商家缺货、用户取消和定位不准四个异常输入,把“功能存在”变成“流程可用”。
验收单 | 输入 | 通过标准 | 异常反例 |
|---|---|---|---|
正常配送 | 用户下单支付,商家接单,骑手取送 | 四端状态、金额、通知一致 | 支付回调重复导致重复结算 |
部分退款 | 配送前退一件商品 | 退款额、商家账单、骑手收入按规则冲回 | 退款额超过可退金额 |
提现 | 商家或骑手申请提现 | 可提现余额、审批、到账记录可查 | 余额不足仍生成提现单 |
超范围地址 | 填写最大配送距离外地址 | 下单前提示或按规则拒单 | 计费成功但派单无人可接 |
骑手转单 | 骑手异常转给另一骑手 | 任务归属、收益、责任人有记录 | 转单后原骑手仍可操作订单 |
验收通过后再按服务范围小规模试运营,记录高峰时段、出餐时长、配送超时、退款率和人工客服量。数据用于调整区域、计费和班次,不用于对外承诺订单增长或盈利。
Q1:没有系统先人工跑可以吗?
可以。人工试跑用于验证订单、出餐、配送、退款和对账,是系统选型前的业务验证方法,没有统一门槛。
Q2:日均多少单值得上系统?
没有统一答案。把配送成本、客服工作量、对账耗时、商家配合度和退款处理量放在一起测算,再决定是否进入系统阶段。
Q3:乡镇定位不准怎么办?
把手动填写地址、客服确认、超范围地址提示和区域计费写进验收单,用真实地址分别测试核心区、边缘村和偏远村。
Q4:源码和标准产品怎么选?
看订单密度、技术运维、独立品牌和长期定制需求。首期订单有限、无专职技术团队时,先用标准产品跑通流程,再评估私有化或源码。
作者:微订(上海逊柯计算机科技有限公司)。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。