支付中的业务逻辑|支付幂等性

支付中的业务逻辑|支付幂等性

前言:何为幂等性?

幂等性就是指“一个操作任意多次执行所产生的影响均与一次执行的影响相同。

幂等性其实是一个数学上的概念,在计算机领域,幂等是指一个方法被多次重复执行的时候所期望的结果要和第一次执行期望的结果保持一致。

支付场景的幂等性问题

很多操作天然具有幂等性,比如查询数据库,获取缓存数据,查询接口等,这些操作不管执行多少次都不会对系统产生影响;而新增、修改和删除可能会导致系统状态发生改变,因此在设计接口时要注意保证其幂等性。

接口幂等性

接口幂等性是指一个接口在多次请求同样的操作时,对系统状态不产生任何影响。具体到支付场景,对于同一笔交易的多次提交,只会产生一次支付结果,并且对账单和余额不会重复扣减,也就是说,无论是因为网络故障、超时等原因,还是由于用户意外或者恶意操作导致多次条件,都不会对支付系统的状态产生任何影响。

保证支付接口的幂等性非常重要,因为一旦出现支付失效、重复扣款等问题,不仅会导致用户体验变差,还可能引发纠纷和信任危机,进而影响到品牌形象和商业利益。

重复支付是怎么产生的?

想要保证支付的幂等,就要了解一下为什么支付是非幂等的,又是哪些场景造成了支付接口的非幂等,这样我们才能”要症下药“,找到解决支付幂等性的合理解决方案。

先来看一套支付流程中的操作:

 1.用户选择支付方式 -> 2.跳转到收银台 -> 3.用户支付成功 -> 4.第三方支付平台回调 -> 5.修改订单状态

导致重复支付的操作是在第三步与第四步。

  1. 首先针对第三步,用户在支付的过程中时可以选择多种付款方式的,如果用户准备微信支付,发现余额不足,或者因为网络原因付款后没有显示成功,又立即切换到了支付宝再次支付,可能会导致同一笔订单产生两笔扣款;再或者用户短时间点击了两次 立即支付 ,同时跳转了两个页面,在一个支付页面上完成了扣款又去另一个界面进行付款。。。

  2. 再来看第四步,用户发起支付后产生待支付订单,但是支付状态是不可控的,支付状态需要由后端从第三方平台【微信、支付宝】的异步回调信息中获取,同样可能因为网络原因后端无法获取到正确的支付信息,导致待支付订单再次产生。

支付幂等方案

解决方案一:前端防抖

经过一些人在实际支付项目中的经验,微信支付平台的扫码支付是不允许同一个订单号短时间内二次创建支付的,但是对于其他平台可能还是需要我们自己手动做出限制.

前端防抖是防止重复支付的第一道防线,整个支付流程就是从用户点击【支付按钮】开始的。对于用户可能短时间连续点击多次支付,所以我们可以在前端进行控制,限制用户在间隔n毫秒内,最多只会执行一次。

仅仅在前端作出限制还不能完全解决重复支付的问题,对于网络原因以及第三方支付平台的异步回调都有可能导致重复支付的发生,因此还要再对后端进行幂等处理。

解决方案二:token令牌

简单理解就是用户初次访问服务器,后端服务器会生成一串加密字符,在其中保存一些信息,返还给客户端,后面客户端再次访问服务器时都会携带这串加密字符,后端解密以后可以获取到之前保存的信息,这串加密字符就是token。

Token可以看成Session的升级版,都能保存信息,但Token的信息是经过加密的,因此更加安全,常用来检验用户的登陆状态。

说回支付幂等性,我们可以在用户提交订单的时候,签发一个Token,并把token保存在redis缓存数据库中,每个订单的token都是不同的,同样一个订单的token只能使用一次,用完即删,那么同一笔订单再次调用支付接口时,发现redis中的token不存在,代表这笔订单已经被支付,就会拒绝请求。

redis + token 的方案在多线程并发的情况的条件下仍然不能避免重复支付,因为上述的操作不是原子性的,我们还需要借助Lua脚本和分布式锁,保证redis获取令牌,生成单号,删除令牌这一系列操作是原子性的。

Lua脚本的示例”

-- 获取传入参数
 local token = ARGV[1]
 local key = ARGV[2]
 local value = ARGV[3]
 ​
 -- 检查 token 是否存在于 Redis 中
 if redis.call("GET", "token:" .. token) then
   -- 如果 token 已经存在,说明该请求已经处理过,直接返回结果
   return redis.call("GET", key)
 else
   -- 如果 token 不存在,则将 token 存储到 Redis 中,并且设置过期时间
   redis.call("SET", "token:" .. token, 1)
   redis.call("EXPIRE", "token:" .. token, 60)
 ​
   -- 执行真正的支付操作,并将结果存储到 Redis 中
   local result = do_payment(value)
   redis.call("SET", key, result)
   redis.call("EXPIRE", key, 3600)
 ​
   -- 返回支付结果
   return result
 end

这个脚本假设 do_payment 是自定义函数,用于执行真正的支付操作。在代码中,我们首先获取传入的 token、key 和 value 参数,然后检查 token 是否存在于 Redis 中,如果存在则直接返回之前处理过的结果,否则将 token 存储到 Redis 中并执行真正的支付操作。最后,将支付结果存储到 Redis 中,并设置过期时间。

通过使用 Redis 和 Token 解决支付幂等性,我们可以保证同一个请求只会被处理一次,从而避免重复支付的问题。

结局方案三:引入“支付中” 的状态

我们可以在支付订单中引入一个“支付中”的中间状态,用户发起支付以后后端要把支付订单的状态设为“支付中”,这样即使是支付没有成功,也可以防止重复支付的产生。

但是这种方式存在『卡单』的问题,因为支付结果需要第三方平台(支付宝,微信)异步回调给后端系统,这其中可能由于各种异常导致第三方平台没有回调支付结果后者后端没有接收到回调信息,从而导致支付订单一直处于 “支付中”,为了解决『卡单』,后端需要加入主动查询的逻辑,去第三方支付平台查询支付结果。

该创作来自于之前查到的某篇文章,如需删除和我联系

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叫一只啦啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值