啥?你这支付提供商不提供推送服务?

165 阅读5分钟

前言

最近接手了一个离职的同事的项目,其中的支付功能老是有漏单的情况发生。怀疑问题出在了回调上,交由同事排查后,始终找不到回调接口在哪。费了九牛二虎之力找到当初的对接文档,确定了真实的没有回调接口,订单状态只能主动查询,这反常的情况确实诡异。

问题定位后,手动发起同步后问题解决。在跟客户说明情况后,说了一句云瓣支付不支持主推之后,不想群里潜伏着云瓣开发,被问候了几轮脏话之后,确定这是人家特性,而不是我们对接了一半。

那么,既然人家不支持,咱得解决这个问题啊。

先说支付为什么必须要有回调

原因很简单:

一、后端无法确切知道用户是否已支付 后端只能向前端下发支付凭证,至于用户是否完成支付,是完全不知道的。采用轮训的方式,不仅浪费系统资源,轮询间隔也没法确定。支付的时效性是一项强要求,是不会允许用户支付后,订单长时间的显示未支付的,用户体验差,也容易造成重复支付的问题(尤其是支持多渠道支付的产品)。

二、前端的支付通知是不可靠的 前端总是会出现各种各样的问题,包括但不仅限于: 支付后不管、网络波动、信号差等等。我们在景区会常常遇到这种情况,我网络差罢了,已支付的订单怎么也一直显示未支付,这种情况就是依赖于前端的通知,造成漏单或者同步延迟了。

通常,一个稳健的支付流程是:前端支付成功后,会发送一个通知回后端,后端接收此通知,如果尚未收到支付商的回调通知,可以发起一个查询请求,以便最快的更新订单状态。如果后端收到了渠道商的回调通知(注意这个接口必须是幂等的,渠道商可能发起多次通知),可以再主动发起一次查询,以便进行二次确认。

这里还有两个问题需要注意:

  1. 前端任何业务的进行,都不应依赖于支付渠道的状态,也不应该被通知接口所阻塞。这个通知接口,作用仅限于通知,后端应该进行异步处理,并且响应202 Accepted状态。
  2. 服务端进行二次确认,源于渠道商的通知,是极容易被伪造的。

对于轮询,支付提供商怎么说?

云瓣骂了半天后,很不好意思的给了我一个建议:

image.png

对于防掉单的方案1,这显然是不可以接受的,不可能等用户进入了系统才确认他的支付状态,直接排除❌

方案2似乎很有道理,我们接收微信、支付宝等支付渠道的回调通知,就是依次递增的。但是我们细细来想,这正确吗? 许多人也会犯这个错误,任务依次增大的轮询间隔,可以有效减少系统压力,并保证数据的一致性,实现起来复杂,看着就很高端。

但是,依次递增的间隔,是基于故障理论的,在时间t1内如果对方无法处理,那么有理由相信在t2(t2>t1)的时间后才有可能恢复。经常线上宕机的小伙伴一定会理解此理论🤣。

而订单状态的变化,是基于用户支付习惯的,这个习惯,同自然界的大部分事物一样,是遵循正态分布的,输入速度、网络状况和余额,是主要的影响因子。对于有明确支付意图的用户来说,极快和极慢完成支付的都是特别少的,绝大多数会在tn(抱歉具体的数值我不知道,大家可以拿起支付宝自行体会一下)时间内完成支付。我们的服务好好的,你们的服务也好好的,我递增间隔干啥呢?因此啊,方案二也排除❌

那么,轮询到底怎么做?

回到轮询问题,假设前端通知的时间为tm(tm可能无限大),我们至少要在min(tm, tn)时,发起第一次请求,而后以固定的间隔t'进行重试,直到得到订单的最终状态(已支付、已关闭(注,大部分支付商的已关闭状态需要调用方自行维护))或者tmax。tmax为统计学上确定的用户支付意愿的下降点,比如一个订单超过5分钟未支付,我们认为他完成支付的可能性是极低的,在这之后可以稍微增大t'的值,以减少服务器的压力。这里t'、tmax只能基于自己的业务实景,确定一个合适的值:间隔太小,增大自己的压力,某些支付渠道也有查询频率/次数的限制;间隔太大,则降低了用户体验,增加了其它订单问题风险。这之间,如果遇到对方服务故障等,我们还需要继续调整轮询间隔。

另外,订单过期时间太长的话,使用轮询也会离谱啊~轮询也只适合订单有效期比较短的场景。

当然,如果我们粗暴的5s一次轮询,似乎也没啥大问题嘛!可是我们是有理想的程序员,我们要按照它应该的样子来实现它🙋‍♂️

最后,有没有更好的办法?

当然,我们更换一个有回调的就可以啦~