县城、乡镇做本地商家入驻的外卖加跑腿,技术上适合用一套多商户平台承载用户、商家、骑手和平台后台四端;是否共用一个小程序入口可按目标版本演示确认。选型时先看四端能否跑通一笔订单,再核对商家入驻字段、跑腿任务与计费、抽成结算、异常处理和部署预算。下面按订单状态、审核数据、计费函数和对账单据给出可验收的实现思路。
首期同时做餐饮外卖、商超和跑腿时,可以把用户账号、地址、服务区域、骑手池和管理入口放在同一套平台里,降低重复注册与重复配置。但外卖和跑腿的订单状态、计价、履约和结算要按业务类型分别建模,否则退款、抽成和骑手收益容易互相冲账。选型时不要只问“支持不支持跑腿”,而要问一笔外卖单和一笔帮买单分别怎样流转、钱怎样分列。
核对层 | 适合共用 | 需要分业务记录 | 演示时看什么 |
|---|---|---|---|
资源 | 用户资料、地址、服务区域、管理入口、骑手池 | 各业务的任务池与权限范围 | 同一用户能否下外卖单和跑腿单,地址和区域能否复用 |
规则 | 品牌入口、基础账号、消息渠道 | 订单状态、计价规则、履约流程、售后责任 | 外卖是否有商家接单环节,跑腿是否可直接进入骑手接单 |
账务 | 统一对账入口与统计口径 | 商家货款、骑手收益、平台服务费、退款冲回 | 一笔帮买单里商品款与服务费是否分列,退款能否回到原单 |
这张表可以直接变成需求清单:共用项写进账号与区域配置,分业务项写进订单类型和结算分录。首期范围建议先限定一个县城主城区或乡镇主区,把餐饮、商超、帮买、帮送、代取快递中的两三类跑腿先跑通,再扩品类和区域。
本地商家入驻要列两张清单:一张是当地市场监管、食品经营等监管要求,按业态和地区核验;另一张是平台审核字段,包括主体资料、许可证或备案信息、门店照片、联系人、结算账户、配送方式等。平台可以把字段做成可配置,但平台配置替代不了监管核验。餐饮商家不能因为平台流程简化就免于监管;小吃摊、果蔬店等主体是否适用简化材料,应按当地规定和实际业态确认。
CREATE TABLE merchant_application (
id INTEGER PRIMARY KEY,
merchant_name TEXT NOT NULL,
subject_type TEXT NOT NULL,
license_no TEXT,
food_permit_no TEXT,
permit_expire_at TEXT,
contact_phone TEXT NOT NULL,
settle_account TEXT,
audit_status TEXT NOT NULL DEFAULT 'PENDING',
audit_note TEXT,
created_at TEXT NOT NULL
);
CREATE INDEX idx_merchant_audit ON merchant_application(audit_status, permit_expire_at);这段表结构只表达平台需要记录哪些审核字段,具体哪些字段必填、证件到期后怎样停单、恢复营业要哪些材料,要按目标版本和当地监管确认。技术上可以把证件有效期作为独立字段,到期前提醒、到期后限制接单,并在审核记录里保留变动内容,方便平台复核。
跑腿首批可先做帮买、帮送、代取快递。每类任务写清取送地址、物品说明、预算、件数、预约时间、凭证和异常处理。费用按起步范围、距离、件数或重量、区域、时段组合。下面的 TypeScript 示例只用于验证计费链路,参数是示例值,不是市场报价;上线时应把费率、起步范围、夜间时段和重量阈值写进配置,并保留历史订单的计价依据。
// order-pricing.ts
// 依赖:Node 18+,typescript 与 tsx
// 运行:npm i -D typescript tsx && npx tsx order-pricing.ts
type BizType = 'takeout' | 'errand';
type ErrandType = 'buy' | 'send' | 'pickup';
interface QuoteInput {
bizType: BizType;
errandType?: ErrandType;
distanceKm: number;
weightKg: number;
isNight: boolean;
itemsAmount?: number;
}
interface QuoteLine {
name: string;
amount: number;
}
interface QuoteResult {
total: number;
lines: QuoteLine[];
}
const BASE_FEE = 4;
const BASE_KM = 2;
const EXTRA_PER_KM = 1.5;
const FREE_WEIGHT_KG = 5;
const EXTRA_PER_KG = 1;
const NIGHT_FEE = 2;
function isInServiceArea(distanceKm: number, maxKm: number): boolean {
return distanceKm > maxKm ? false : true;
}
function quoteErrand(input: QuoteInput): QuoteResult {
const lines: QuoteLine[] = [];
lines.push({ name: '起步价(含2公里)', amount: BASE_FEE });
const extraKm = Math.max(0, input.distanceKm - BASE_KM);
if (extraKm > 0) {
lines.push({ name: '超程费', amount: Number((extraKm * EXTRA_PER_KM).toFixed(2)) });
}
const extraWeight = Math.max(0, input.weightKg - FREE_WEIGHT_KG);
if (extraWeight > 0) {
lines.push({ name: '超重费', amount: Number((extraWeight * EXTRA_PER_KG).toFixed(2)) });
}
if (input.isNight) {
lines.push({ name: '夜间服务费', amount: NIGHT_FEE });
}
const itemsAmount = input.itemsAmount ?? 0;
if (input.errandType === 'buy' && itemsAmount > 0) {
lines.push({ name: '商品预估价(不计入服务费)', amount: Number(itemsAmount.toFixed(2)) });
}
const total = lines
.filter((line) => line.name.startsWith('商品预估价') === false)
.reduce((sum, line) => sum + line.amount, 0);
return { total: Number(total.toFixed(2)), lines };
}
const cases: QuoteInput[] = [
{ bizType: 'errand', errandType: 'send', distanceKm: 1.2, weightKg: 2, isNight: false },
{ bizType: 'errand', errandType: 'buy', distanceKm: 3.5, weightKg: 6, isNight: true, itemsAmount: 38 },
{ bizType: 'takeout', distanceKm: 3.5, weightKg: 6, isNight: true },
];
for (const item of cases) {
if (item.bizType !== 'errand') {
console.log(item.bizType, '走外卖计价规则,本示例只展示跑腿');
continue;
}
console.log(JSON.stringify(quoteErrand(item), null, 2));
}
console.log('超范围校验:', isInServiceArea(3.5, 3));输入 | 校验点 | 按上述函数推得的预期结果 |
|---|---|---|
帮送1.2公里、2公斤、日间 | 起步范围内 | 服务费合计4元,只出现起步价 |
帮买3.5公里、6公斤、夜间、商品预估38元 | 超程、超重、夜间、商品款分列 | 起步4元、超程2.25元、超重1元、夜间2元,服务费合计9.25元;商品预估价38元单列,不计入服务费 |
外卖单进入跑腿示例 | 业务类型隔离 | 提示走外卖计价规则,不套用跑腿费率 |
3.5公里,服务上限3公里 | 超范围地址 | 返回false,应在下单或派单前拦截 |
这些结果是按示例函数推得的预期输出,不是现网实测。正式验收时应保存代码版本、运行环境和实际输出,并把费率调整的生效时间记录下来,避免历史订单失去计价依据。
抽成结算不要只看比例,要先用一笔正常外卖单、一笔跑腿帮买单、一笔部分退款、一次提现走完。确认抽成基数是商品款、服务费还是合并,优惠由谁承担,配送费、退款冲回、账期和手续费怎样记录。平台、商家、骑手三类账应能按订单对应,支付通道和结算主体按合同核实。
CREATE TABLE settlement_entry (
id INTEGER PRIMARY KEY,
order_no TEXT NOT NULL,
biz_type TEXT NOT NULL,
account_type TEXT NOT NULL,
amount_cents INTEGER NOT NULL,
created_at TEXT NOT NULL
);
SELECT order_no,
SUM(CASE WHEN account_type = 'user_paid' THEN amount_cents ELSE 0 END) AS paid,
SUM(CASE WHEN account_type = 'merchant_payable' THEN amount_cents ELSE 0 END) AS merchant,
SUM(CASE WHEN account_type = 'rider_income' THEN amount_cents ELSE 0 END) AS rider,
SUM(CASE WHEN account_type = 'platform_fee' THEN amount_cents ELSE 0 END) AS platform,
SUM(CASE WHEN account_type = 'refund' THEN amount_cents ELSE 0 END) AS refund
FROM settlement_entry
GROUP BY order_no;这张查询用于把同一订单的用户支付、商家应付、骑手收入、平台服务费和退款分列出来。是否满足某个等式取决于抽成基数和优惠承担规则,验收时按项目规则逐单核对,而不是只看汇总比例。部分退款要检查商品款与服务费分别退回多少;已经结算的订单,售后调整是冲减、追溯还是进入后续账单,应提前写清。
单据 | 需要记录 | 核对重点 |
|---|---|---|
正常外卖单 | 用户实付、商家应结、骑手收入、平台服务费、优惠承担 | 四端状态一致,金额能按订单对应 |
跑腿帮买单 | 商品款、跑腿服务费、骑手收益、平台服务费 | 商品款与服务费分列,不把商品预估价计入服务费 |
部分退款单 | 退款商品、退款金额、配送费处理、优惠冲回 | 退款能回到原订单,已结算订单有调整记录 |
提现记录 | 可提现金额、手续费、申请状态、到账状态 | 商家与骑手分别查看,审批和通道状态可追踪 |
上线可按资料、配置、演练、试运营推进:准备主体与账号资料,配置区域与计价,录入商家商品,配置骑手与权限,开通支付,跑测试订单,再培训并小范围试运营。演练订单至少覆盖正常配送、到店自提、用户取消、商家缺货、商家拒单、骑手异常、超范围地址、优惠参与、部分退款和不同角色访问。每类记录操作步骤、订单状态、通知对象和金额变化。
异常处理要提前定好接手人:超范围地址在下单或派单前拦截;收件人不在时记录到达时间、联系情况和后续安排;重复支付回调要保证同一请求只形成一次有效处理;骑手转派后要能查到原任务、新任务和收益归属。把这些场景写进上线清单,比只看功能菜单更接近真实经营。
监管要求和平台审核字段要分开。平台可以配置材料项,但餐饮等业态是否适用简化材料,应按当地监管和实际业态确认,不能把平台字段当成监管规则。
可以按业务组合并统一品牌入口,是否共用同一个小程序、两类业务如何在首页分区展示,需要按目标版本演示确认。技术上先确认用户、地址、区域能否复用,再确认订单和结算是否分业务记录。
两种口径都可能在项目中出现,关键是写清楚。跑腿帮买宜把商品款与服务费分列,外卖则要明确包装费、配送费、优惠和平台服务费各自归属。用一笔含优惠订单和一笔部分退款单核对,比只看比例更可靠。
历史项目价格只能作为方案背景,不能当成统一报价。当前费用要按模块、终端、部署方式、定制范围、培训和维护内容逐项确认,并区分一次性费用与续费。
上线前把首期片区、首批商家类型、跑腿任务类型、计费参数和结算主体列成一页需求清单,用正常外卖单、跑腿帮买单、部分退款单和提现记录各走一遍;记录状态变化、通知对象和金额分列,再决定模块组合和实施节奏。
作者:微订(上海逊柯计算机科技有限公司)。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。