为什么对账这么重要

支付系统的钱,微信说扣了、商户说没到、用户说付了三次——没有对账,这三方永远各说各话。对账就是每天把平台侧的交易流水与微信侧的资金流水逐笔比对,确认每一笔钱都名副其实。

对账不是可选项:通道掉单、回调丢失、重复支付、金额篡改,全靠对账兜底。上线第一周就把对账做起来,比上线三个月后再补要省十倍力气。

对账是支付系统最后的防线,其他环节都可以出错,对账不能缺席。
对账三要素
  • 账单微信侧资金明细。。
  • 流水平台侧交易明细。。
  • 差异逐笔比对与处置。。
微信支付对账配图:发光天平与空白账本轮廓,中间双向箭头表示核对
日清日结,账目分明。

下载与解析对账单

微信支付对账单按日生成,可在商户平台下载,也支持通过接口拉取。对账单包含交易时间、商户单号、微信单号、金额、状态等字段,一天一文件,格式固定。

# 拉取对账单接口(示例:Node.js)
const res = await pay.downloadBill({ billDate: '20261028' });
const lines = res.data.split('\n').filter(Boolean);
// 首行为表头,末行为汇总行,中间为明细
for (const line of lines.slice(1, -1)) {
  const [time, mchId, outTradeNo, wxNo, type, amount, status] = line.split(',');
  // 进入逐笔比对流程
}

下载后先校验文件签名与汇总行:总笔数、总金额与明细累加对不上,说明账单本身有问题,立即停止处理并告警。

逐笔核对与差错处理

以微信单号或商户单号为键做集合比对,得到三类结果:双方都有、只有我方、只有微信侧。只有一方的记录要进入差错池,人工或自动化规则处置。

常见差错:我方已扣款但微信无记录(疑似伪造或掉单)、微信有扣款但我方未入账(回调丢失,需要补单)、金额不一致(优先级最高,必须冻结处理)。每类差错定义超时时间,超时升级。

退款流程与安全

退款接口同样要过签名与证书校验。退款金额不能超过实收金额,退款原路返回,状态要可查询。设计上把退款做成独立领域:申请、受理、转账、完成、失败,每一步落库。

# 退款接口调用前必查
const order = await orderRepo.getByNo(outTradeNo);
if (!order || order.status !== 'SUCCESS') {
  throw new BizError('订单不可退款');
}
if (refundAmount > order.paidAmount) {
  throw new BizError('退款金额超限');
}
// 幂等:同一退款单号只允许一笔
await refundRepo.tryCreate(refundNo, order.id, refundAmount);

退款权限要隔离:运营人员只能发起不能放行,财务复核后才能执行,系统日志全程留痕。

回调验签与幂等

支付与退款结果都通过异步回调通知。回调第一件事是验签,第二件事是幂等处理。验签不过直接拒绝;同一通知重复到达,只有第一次能改变状态。

// 回调处理:验签 + 幂等
if (!verifySign(req.body)) return res.status(400).end();
const { outTradeNo, returnCode } = req.body;
const ok = await tx(async () => {
  const o = await orderRepo.lock(outTradeNo);
  if (o.status === 'PAID') return false; // 已处理
  await orderRepo.markPaid(o.id);
  return true;
});
res.json(ok ? { code: 0 } : { code: 0 }); // 都返回成功,避免重发

数据库唯一索引 + 事务是幂等最可靠的实现方式,单纯用内存标记在重启后就会失效。