2.1 前言
- 轧帐:对账系统的工作,是发现有差异的记录
- 平帐:通过人工或者自动的方式,解决上述差异
- 对电商系统来说,每一笔交易,在所有相关主体侧都要能对得上
- 交易主体:如发起人是个人,必须能够从个人交易历史记录中找到这笔交易。但大部分人不会保留电子记录,所以一般是提供可以下载的账单或交易记录,让用户自己对去
- 交易对手:一般是商户。商户侧对账处理同用户侧,也仅仅提供对账单
- 交易渠道侧:这是对账的重点,一是核实交易流水,二是核实交易佣金,毕竟是租用人家通道做结算的
- 需要对账的记录主要是交易记录和退款记录
2.2 对账处理流程
一般来说,对账流程涉及到如下步骤:渠道对账单下载、本地交易记录准备、轧账、平账
2.2.1 渠道对账单下载
- 银行、第三方支付、银联等,基本都会提供对账单下载的功能。不过也有少数工作做不到位或者太到位的银行,只提供账单查询后台,不提供对账单下载功能
- 对开发人员来说,这里有几个坑
- 对账单格式不一:txt、xml、csv 的都有。为了后续能够统一处理,在账单下载完成后,需要进行标准化处理
- 下载方式不一:HTTP、HTTPS、FTP 的都有。下载程序需要按照渠道的协议来处理
- 下载时间不一:一般是凌晨 1 点后,到中午 12 才能用的也有。如果在预定的时间取不到数据,需要注意重试读取
- 稳定性差:FTP 服务器出问题是常有的事。渠道侧解决方案往往就是重启。所以重试机制是必要的
- 技术方面
- HTTP(S):用 apache http client 即可实现连接池和断点续传
- FTP:可使用 Apache Commons Net API
- 不管是哪一个,都需设置重试次数和连接超时。重试次数和间隔的设置需要小心,重试太频繁,容易把服务器打死;时间间隔太大,又会阻塞后续处理步骤。5~10 分钟是一个合适的重试间隔区间
- 连接超时:指在服务器出现问题时,连接在指定时间内获取不到数据即自动断开
2.2.1.1 第三方支付的对账单情况
渠道 |
对账周期 |
账单提供方式 |
账单文件格式 |
支付宝 |
每天 2:10 |
HTTPS |
xml |
支付宝退款 |
每天 3:10 |
HTTPS |
xml |
百付宝 |
每天 7:00 |
FTP |
txt |
百付宝退款 |
每天 7:00 |
FTP |
txt |
微信支付 |
每天 10:30 |
HTTPS |
txt |
微信退款 |
每天 10:30 |
HTTPS |
txt |
2.2.1.2 银行直连的对账情况
银行 |
对账形式 |
对账周期 |
打款周期 |
交行 |
接口/商户对账系统 |
日对账 |
日结(T+1) |
建行 |
接口 |
日对账 |
日结(T+1) |
工行 |
登录网银的方式手动下载 |
日对账 |
日结(T+1) |
浦发 |
信用卡-登录自助平台;借记卡-接口 |
日对账 |
日结(T+1) |
农行 |
银行定时推送对账文件 |
日对账 |
日结(T+0) |
中行 |
银行定时推送对账文件 |
日对账 |
日结(T+1) |
招行 |
银行定时推送对账文件 |
日对账 |
日结(T+1) |
2.2.2 渠道对账单标准化
- 微信的对账单:csv 格式,逗号隔开。包括字段
- 交易时间:在微信侧的支付完成的时间。这个时间会成为一个陷阱
- 公众账号ID、商户号、子商户号、设备号:这些信息需要做验证,确保是自己的单子
- 微信订单号、商户订单号:对单的核心
- 微信订单号:微信侧产生的订单号,在微信支付接口返回值中有。但是万一收不到这个返回值,那在本地记录中可能就空了
- 商户订单号:我们发送给微信的订单号,一般用这个来做对单依据。两边的数据中都会有这个值
- 用户标识、交易类型、交易状态、付款银行、货币种类、总金额、企业红包金额:对单的核心字段,必须确保双方是一致的
- 商品名称、商户数据包、手续费、费率:可选验证
- 某宝的账单:txt 格式,空格隔开。包括字段
- 商户订单号、交易流水号、交易时间、支付时间、付款方、交易金额、交易类型、交易状态
- 每个渠道的账单格式不尽相同,在得到账单后,下一步是对账单做标准化处理,这样轧帐以及后续工作可统一处理
- 标准化后的账单数据可以放在文件系统或者数据库中。这取决于交易数据量
- 每天百万以上的量,使用文件系统比较合适。如 hdfs
- 数据库操作相对比较慢,也浪费资源
- 基于文件系统的标准化涉及如下内容
- 文件格式标准化:统一使用 csv、json 或 xml 格式。如使用 hadoop 或者 spark 对账,使用 csv 是个不错的选择
- 文件存储统一化:文件目录、文件名都需要遵循统一命名规范
2.2.3 本地交易记录准备
- 本地交易记录的准备,总的来说有如下方法
- 直接用原始数据:鉴于大部分系统使用的是 MySQL,也意味着在 MySQL 上做对账
- 对账时需要大量的数据查找工作,必然会影响线上业务
- 在数据规模较大(如超过 100 万)时不太合适
- 使用备库执行对账:既简单,也不影响线上业务。是典型的空间换时间的做法
- 如果业务大到需要分表分库才能处理,那对账数据准备也不一样
- 使用分库不现实,因为分库一般是按照主体 id,而不是渠道 id 来分库,这样对账需要在多个库上进行,会降低效率
- 对分表分库建立从库非常耗费资源。这种情况下,需要同步一份数据到文件系统(hdfs),或 NoSQL 数据库
- 交易记录是支付系统核心数据,有大量的应用(如信用、风控)都需要交易记录数据。这些应用对交易记录的需求还不完全一致,为了提升性能,交易记录会使用异步的方式来将数据投递给使用方
- 交易记录在入库时,投递消息到消息系统中
- 使用方监听这个消息,一旦收到新消息,则从交易记录库中查询数据,获取数据并更新到库中
2.2.4 轧帐
- 轧帐:按照客户订单号来比较本地交易记录和渠道交易记录是否一致
- 从算法角度,是计算两个数组的差异
- 在单机运行时,可采用的算法不少。
- 推荐采用 mapreduce 来轧帐,优势是可以按照订单号将渠道提供的记录和本地记录 shuffle 到同一个reduce 处理上,这样就可以很容易进行数据比对
- 轧帐中最大的坑,莫过于切分点的问题
- 如以整 0 点为切分点,那存在一个问题,本地 23:59 发起的交易,到了渠道侧,可能会在 00:01 处理,这一笔交易变成第二天的帐了
- 实际处理中,一笔交易在渠道侧处理,花上几分钟都有可能
- 对于切分点附近无法确认的帐,做一个时间窗,在时间窗内的数据,留待第二天对账时继续处理
2.2.5 平帐
- 发现两边不一致的数据,
- 数据量不大时:记录起来,人工甄别
- 数据量很大:人工处理成本太高。这个没有统一的处理方法,需要对有问题的数据分析,然后做自动处理。
- 针对交易记录的对账的处理,主要有如下情况
- 本地未支付,支付渠道已支付:这主要是本地未正确接收到渠道下发的异步通知导致。一般处理是将本地状态修改为已支付,并做响应的后续处理,比如通知业务方等
- 本地已支付,支付渠道已支付,但金额不同:需要人工核查
- 本地已支付,但支付渠道中无记录;或本地无记录,支付渠道有记录:在排除跨日因素外,这种情况非常少见,需要了解具体原因后做处理
- 针对退款的对账处理,主要有如下情况
- 本地未退款,支付渠道已退款:以支付渠道为准,修改本地为已退款状态,并触发后续处理
- 本地已退款、支付渠道已退款,但金额不同:需要人工核查
- 本地已退款,但支付渠道无记录;或本地无记录,支付渠道有记录:在排除跨日因素外,这种情况非常少见,需要了解具体原因后做处理