当前位置: 当前位置:首页 > 探索 > 支付系统设计正文

支付系统设计

作者:热点 来源:焦点 浏览: 【大 中 小】 发布时间:2026-10-17 06:12:58 评论数:

支付系统的支付核心不是支付,是系统记账。用户点下“付款”按钮的设计那一瞬间,钱并没有真的支付从银行卡飞到商户账户里。真正发生的系统事是:系统在数据库里写了两条记录,一条记在用户头上叫“应减”,设计一条记在商户头上叫“应加”。支付这两条记录必须同时成功或同时失败,系统否则账就乱了。设计

我见过一个真实的支付事故。某平台做活动,系统用户下单后调用支付网关,设计网关返回超时。支付前端等了三秒没收到响应,系统提示“支付失败”。设计用户又点了一次。第二次成功了。但第一次的请求其实在网关那边也成功了,只是回执丢了。结果用户被扣了两次钱,商户只收到一笔订单。对账的时候才发现,中间差了整整47笔这样的重复扣款。问题出在哪?出在支付系统没有做幂等。

幂等不是可选项

幂等的意思是:同一个请求执行一次和执行一百次,结果一样。实现方式不复杂——给每笔交易生成一个唯一ID,服务端收到请求先查这个ID有没有处理过。处理过就直接返回上次的结果,没处理过才走扣款逻辑。但很多团队早期为了赶进度,把这一步省了。省的时候觉得“哪那么巧会重复”,上线后才发现用户手抖、网络抖动、前端重试,处处都是坑。

另一个容易被忽视的点是异步通知。支付网关扣完用户的钱,得告诉商户“钱到了”。这个通知必须是异步的,不能同步等。同步等的话,商户服务器卡一下,网关线程就被占住了。高峰期几千个线程堵在那里,整个支付链路全挂。异步通知也有讲究:不能只发一次,要按递增间隔重试,比如15秒、30秒、3分钟、10分钟。商户收到通知后必须返回一个约定好的字符串,比如“SUCCESS”,否则网关认为你没收到,继续重试。这里有个细节——商户处理完业务逻辑再返回成功,别先返回成功再去处理。先返回成功的话,万一后面处理失败了,网关不会再通知你,这笔单子就永远悬着。

对账是最后一道防线

再好的实时系统也会出错。每天凌晨,支付系统必须做一件事:拿自己的流水和渠道(银行、微信、支付宝)的流水逐笔比对。比什么?比金额、比状态、比手续费。不一致的挑出来,人工介入。我见过一个团队,对账程序跑了半年没人看结果。直到某天财务发现账上少了十几万,翻记录才发现对账文件里每天都有几条“金额不一致”,但没人处理。原因很简单:渠道那边扣了手续费,他们这边没扣。每笔差几毛钱,攒了半年就成了一大笔。

对账还有个坑:时间窗口。你不能拿今天0点到24点的流水去和渠道对,因为渠道的日切时间可能不是0点。有的银行是23点,有的是凌晨1点。时间窗口对不上,两边永远对不平。正确做法是拿渠道给的对账文件里的时间范围为准,自己的流水按那个范围去查。

说点实在的

支付系统不需要花哨的架构。核心就三件事:记账准确、状态可查、异常可追。记账用数据库事务保证原子性,状态用状态机管理(待支付、支付中、成功、失败、关闭),异常用日志和告警兜底。别一上来就搞分布式事务、TCC、Saga,那些是给日订单千万级以上的系统准备的。日订单十万以下,单库单表加乐观锁足够跑很久。

最后说个观察:很多支付系统的bug不在支付本身,而在业务系统怎么调支付。比如业务系统先创建订单再调支付,但订单创建失败了支付却成功了,钱收了没订单。或者业务系统把支付结果回调当成了可靠消息,回调丢了就认为没支付。支付系统只保证“钱的状态是对的”,不保证“你的业务逻辑是对的”。这个边界划清楚,能少踩一半的坑。