所有支付公司都对资损(资金损失)看得很重,轻则钱没了,重则舆论风波,要是引起监管介入,更是吃不了兜着走。
常在河边走的支付人,如果想少湿鞋,一定要了解资损防控体系建设。
资损防控是一个很庞大的体系,本文尝试从实用性着手,化繁为简,论述资损防控的本质,如何防,如何控,以及一些典型场景及应对手段。




背景:
世界各国的币种最小单位是不一样的,比如人民币最小单位是分,日元最小单位是元,同一个币种在不同的渠道接口中使用的单位也不一样,有些使用元,有些使用币种最小单位。
问题:
没有经验的工程师喜欢long或double存储金额,手动加减乘除,特别容易发生金额放大或缩小100倍。比如上游传的是元,以为传的是分,直接乘以100处理,资损发生。

解决方案:
背景:
幂等性是一个数学和计算机科学术语,用于描述无论操作执行多少次,都产生相同结果的属性。在软件行业,应用极其广泛,当我们说一个接口支持幂等时,无论调用多少次,对系统造成的业务结果是一致的。(注意这里说的是业务结果)
问题:

解决方案:
背景:
有些银行的订单号或交易请求号只有6位(所谓短号),用完后重新从1开始循环。
有些收单系统使用8位流水号。
系统升级可能导致流水重复。
问题:
支付时渠道返回“重复交易”,支付平台通常会推进到“支付中”或“未知”,然后调渠道查询接口,这时查到的是上一笔支付的结果,平台推进状态为“成功”,资损发生。
内部应用或商户请求支付平台的收单系统也有类似问题。
解决方案:
背景:
每个渠道都有自己的返回码标准,内部各应用也有自己的返回码标准,返回给商户的也有一套收单定义的返回码标准。
有些渠道的返回码分为两级,有些只有一级。有些还分为“操作成功”和“业务成功”。所谓“操作成功”就是接口导致受理成功,但是不代表业务是否成功,只有“业务成功”才代表交易的成功。
问题:
解决方案:
背景:
渠道异步通知回来可能比同步接口返回更快,内部应用之间消息、任务调度都无法保证顺序性。
问题:
除了渠道交易,内部应用之间的交互也可能有类似情况。
解决方案:
背景:
支付平台所有的操作都是需要授权的,比如商户需要使用哪种支付方式,费率是多少,适用于哪种业务,都有明确规定。对接的外部渠道,通常都有线下环境(也称为沙箱环境)、生产环境。
问题:
解决方案:
背景:
所有的交易都会与数据库进行交互,存在所谓的乐观锁和悲观锁的争论。
问题:
数据库操作往往需要同时操作多张表,也就是要求多表之间的事务性,但是乐观锁是做不到事务性的(真要做到的技术成本也很高)。一些研发工程师引入所谓的乐观锁,经常出现事务问题。
解决方案:
背景:
所有的交易单据都是有状态的,比如支付中,成功,失败等。
问题:
经验不足的工程师,经常犯下面的错:
解决方案:
背景:
多线程无处不在,且还可能为解决一些特殊场景,引入线程变量或线程池操作。比如一些变量不想通过接口或函数显式传递,或者为了提高性能引入线程池等。
问题:
解决方案:
背景:
所有的业务系统都面临架构升级,必然会出现所谓【新老系统 + 新老数据】的兼容问题。
问题:
在互联网应用中,所有发布都是灰度发布,数据库的变更也是一样。如果考虑不周全,大概率会出现线上问题,甚至资损。
比如:在灰度发布过程中,出去有可能是新代码,也可能是老代码,渠道异步回调通知时,有可能回调到新代码,也有可能回调到老代码。
数据库结构变更或数据变更也是如此,内部各应用之间的调用也是如此。
架构升级后,模型如果也做了升级,对历史数据的处理,到底是由老系统处理,还是新系统兼容处理?
解决方案:
背景:运营操作在支付平台的日常运营工作中无处不在,比如配置营销券,手动发起打款等。
问题:只要是人操作,一定无法保证百分之一百可靠。比如配置给拉新的营销,忘记设置使用条件,打款多打了一笔等,全部是资损。
解决方案:
在发布或有变更时,重点监控成功率和提交量变化,如果成功率有明显下降,一定要及时回滚。
此外,成功率不仅仅影响信息流,还有可能隐藏着资损。比如在支付场景下,用户扣款成功,但是因为内部的BUG,导致支付单推进到了失败,从表象上看就是支付成功率跌了,但实际用户已经有了资损。
资损的发现更多的仍然依赖对账来解决。
对账需要对什么?前面有说过,资损风险有四大类:状态不符合预期,金额不符合预期,交易笔数不符合预期,越权交易。
对应的,对账主要对三个:状态,金额,交易笔数。

一般的支付平台都会有内部系统之间的两两对账,这种对账主要是信息流层面的对账,主要勾兑状态、金额、笔数等数据的一致性。
再细分,还可以拆成实时对账和离线对账。
实时对账一般就是监听数据库的binlog,当数据有变动时,延时几秒后请求双方系统的查询接口,查到数据后进行对账。
离线对账一般就是把生产数据库的数据定时清洗到离线库(一般还可以分为天表和小时表),然后进行对账。

第一层是信息流对账。我方流水和银行清算文件的流水逐一勾兑。可能会存在长短款情况。
第二层是账单对账。就是把我方流水汇总生成我方账单,然后把银行流水汇总生成银行账单,进行对账。可能会存在银行账单和我方账单不一致的情况,比如共支付100万,渠道分2次打款,一笔98万,一笔2万。
第三层是账实对账。就是我方内部记录的银行头寸和银行真实的余额是否一致。可能存在我方记录的头寸是220万,但是银行实际余额只有200万的情况。
每个公司内容的应急流程都不一样,但都有一个共识:资损发生后,需要立即启动应急,也就是线上故障的优先级高于手头的项目研发。因为在资损发生的前几个小时,追回损失的概率是很高的。
如果是资损故障已经有预案,就根据预案执行。如果是新的资损故障,需要及时上报主管,以便及时处置。
需要特别注意的是,一线工程师在发现资损故障后,一定要第一时间上报给自己的主管。原因有2个:
资损故障都需要复盘,但是复盘的目的不只是为了追责,更是要看以后如何做得更好,以堵住类似所有的漏洞。
复盘内容至少包括以下几个内容:
在制定待改进项时,需要注意以下几点:
核心思路:每个人的能力是不一样的,同一个人在不同时间的状态也是不一样的。所以需要技术手段来确保不管什么人在什么状态下,都能防住资损或能高效处置资损。
“预则立,不预则废”,这句古老的格言放到资损防控语境下尤其贴切。
预案需要针对可能发生的资损场景,制定详细的对策及处置流程,以便在故障发生时,可以高效地处置。
预案通常需要包含以下内容:
预案做得再漂亮,如果没有提前演练,真出现问题时,往往是一团糟。这就要求我们不定期做一些演练。
演练通常分为两种:有损演练,和无损演练。需要根据实际情况选择。
常用的无损演练一般就是注入日志,或者修改对账脚本,对生产环境的数据没有影响。一方面检查是否能及时告警,以及告警出来后,值班的工程师是否及时接手和处置,处置过程是否得当。
有损演练直接触发线上交易。比如通过线下测试环境调用银行生产环境提现一笔小金额,就是一种典型的有损演练,因为这笔钱最后可能因为各种原因无法转账到备付金账户里去。这种有损演练,只要控制在小金额范围内,风险是可控的,且能更真实反映各方处置资损时的真实能力水平。
支付系统的产品设计,除了会员实名认证这种纯信息流外,其它大部分情况下都需要同时考虑以下情况:
资损防控不是点或线就能搞定的事,需要组合多种能力,才能建设出完善的资损防控体系。每个场景都可以考虑使用多种手段来解决,比如乱序的场景,就要使用幂等,状态机,终态设计,数据库一锁二判三更新等多种手段。
中学时读到扁鹊的故事,很有趣:“魏文侯问扁鹊曰:‘子昆弟三人,孰最善为医?’对曰:‘长兄病视神,未有形而除之,故名不出于家。仲兄治病,其在毫毛,故名不出于闾。若扁鹊者,镵血脉,投Du药,副肌肤,故名闻于诸侯。’”。
资损防控也是如此,做得最好的,恰如同扁鹊的长兄,往往大家都不知道。希望本文能为各位在资损防控建设方面有所帮助,早日修成扁鹊长兄的才能:“未有形而除之”。
我刚毕业时进入的是传统行业,后来转战互联网支付,在我负责的第一个互联网支付项目要上线时,被当时的总监连问了几个问题:“发布手册有吗?灰度方案是什么?可能存在哪些异常场景,对应的预案有吗?发布出问题能否回滚?验证方式是什么?... ...”我哑口无言,因为以前在传统公司做的项目都是停机发布,且只管写代码,不管发布,哪有这些考虑。
再后来亲自处置了很多次资损事件,比如:
案例数不胜数,于是只好安慰自己说:“那些还没有经历过资损的支付人,只是因为在支付这个行业呆的时间还不够久罢!”。