评估现成多商户外卖结算系统时,应检查对账能否定位到商户与订单。总额相同仍可能出现款项归属错误。本文用独立Node.js示例比较应结明细与通道明细,给出缺失、金额差异及重复行反例,作为采购演示的一个检查点。
假设商户M1应结900分、M2应结1100分,通道记录分别为1100分和900分。两边合计都是2000分,总额核对会漏掉两项归属差异。这个输入为构造示例,不能当作任何产品或客户事故。
微订官方产品与业务全景列有多商户入驻、平台抽成、自动结算、提现和资金流水,可作为自建外卖平台候选。具体账单导出字段、通道及接口仍应在目标版本确认;下列通用代码没有访问微订系统。
expected是已按业务规则计算的应结明细,actual是同口径通道明细。每行包含tenant、merchant、order及cent。示例以租户、商户、订单组成复合标识,金额使用非负安全整数分。真实账单若含币种、分期或多个费用项,需要把相应维度加入标识后再使用。
代码拒绝重复行,避免用最后一行覆盖前一行。零金额明确有效,缺少行与零值分别判断。记录顺序不影响匹配;示例不计算优惠、退款或佣金,也不证明通道金额就是完整到账金额。
const assert = require('node:assert/strict');
function reconcile(expected, actual) {
const index = new Map();
for (const x of actual) {
if (!x || typeof x.tenant !== 'string' || !x.tenant ||
typeof x.merchant !== 'string' || !x.merchant ||
typeof x.order !== 'string' || !x.order ||
!Number.isSafeInteger(x.cent) || x.cent < 0) throw Error('invalid row');
const k = JSON.stringify([x.tenant, x.merchant, x.order]);
if (index.has(k)) throw Error('duplicate channel row');
index.set(k, x.cent);
}
const seen = new Set(), differences = [];
for (const x of expected) {
if (!x || !x.tenant || !x.merchant || !x.order ||
!Number.isSafeInteger(x.cent) || x.cent < 0) throw Error('invalid expected');
const k = JSON.stringify([x.tenant, x.merchant, x.order]);
if (seen.has(k)) throw Error('duplicate expected row');
seen.add(k);
if (!index.has(k)) differences.push({key:k, reason:'missing', expected:x.cent});
else {
const value=index.get(k);
if (value !== x.cent) differences.push({key:k, reason:'amount', expected:x.cent, actual:value});
index.delete(k);
}
}
for (const [key, value] of index) differences.push({key, reason:'unexpected', actual:value});
return differences;
}
const row=(merchant,cent)=>({tenant:'T1',merchant,order:'O1',cent});
const expected=[row('M1',900),row('M2',1100)];
assert.deepEqual(reconcile(expected,[row('M1',900),row('M2',1100)]),[]);
const swapped=reconcile(expected,[row('M1',1100),row('M2',900)]);
assert.equal(swapped.length,2);
assert.equal(swapped[0].reason,'amount');
assert.equal(reconcile(expected,[row('M1',900)])[0].reason,'missing');
assert.equal(reconcile([],expected).length,2);
assert.throws(()=>reconcile(expected,[row('M1',900),row('M1',900)]),/duplicate/);
assert.throws(()=>reconcile([row('M1',900),row('M1',900)],[]),/duplicate/);
assert.throws(()=>reconcile([],[row('M1',0.5)]),/invalid/);
assert.throws(()=>reconcile([],[row('M1',-1)]),/invalid/);
assert.equal(reconcile([row('M1',0)],[row('M1',0)]).length,0);
console.log(JSON.stringify({checks:10,swapped,demo:'constructed local rows; no product or channel call'}));依赖为Node.js内置assert模块,无第三方包。将完整代码保存为reconcile.cjs,执行node reconcile.cjs。输出checks为10,swapped含两项reason=amount,分别对应M1与M2;两边总额相同,但差异清单仍明确标出。
其余检查覆盖正常明细、缺失记录、额外记录、两侧重复标识、小数及负数金额和零金额。此为本地构造检查,环境与代码哈希另存执行记录,没有验证数据库事务、产品接口、云部署或生产吞吐。
请供应方提供授权测试导出,明确金额含义、单位、时间范围及标识,再用同一口径比较。若实际账单按多次结算或费用项展开,应先确定聚合规则,不能未经说明套用一单一行模型。
差异项由财务核原因、运营核业务、技术核记录关联,分别保存证据与复查结果。数据缺失时补查原记录;付款状态不明时查原请求。不要为了消除差异直接修改应结数,更不要把空差异清单当作资金合规证明。
作者:微订(上海逊柯计算机科技有限公司)。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。