如何防止订单重复支付?

大家好,我是磊哥,想必大家对在线支付都不陌生,今天和大家聊聊如何防止订单重复支付。

看看订单支付流程

我们来看看,电商订单支付的简要流程:

09363bf2c713757a90ed6eb87233a39c.png
订单钱包支付流程

从下单/计算开始:

  1. 下单/结算:这一步虽然不是直接的支付起点,但是支付相关的金额等等信息都来自结算,此时订单的状态是未支付

  2. 申请支付:用户选择申请支付,客户端调用支付服务,此时在系统内产生一笔支付流水,这笔流水的状态是未支付

  3. 发起支付:支付服务调用三方支付,通常这种钱包类的支付,在发起支付这一步,会响应一些支付的链接,客户端会对链接进行对应的处理。

  4. 钱包支付:用户进行支付,通常是通过对应的钱包进行的,大家可以回忆一下自己在购物中,支付的过程,不同的端,对钱包支付的处理是不太一样的:

    3808e4d0c584860ec53719eb7a17fd62.png
    京东PC端支付页
  • APP端: 在国内,购物大部分都是在APP端,产品经理会想法设法把用户带到APP,为什么我的示例图都用京东,不用淘宝呢?因为我拿UC打开淘宝,会直接跳转APP。

    APP端的钱包支付,我们应该都非常熟悉,一般是拉起钱包,支付。

    d12faee4fccafd3dee8c95306dee36d1.png
    APP支付
  • WAP端:手机的网页站,WAP端的支付一般是直接拉起对应的钱包,如果拉起钱包失败,就跳转界面

    f3f14410bac735a3ab23cd78b0ab4763.png
    京东支付WAP端
  • PC端:PC端,通常是打开收银台,展示一个二维码,通过钱包扫码支付,下面是京东的微信支付扫码页

支付回调:用户完成支付后,三方支付平台,会回调商户,通知支付结果。

同步订单状态:支付服务在确认支付完成后,会向订单服务同步支付的结果,订单服务变更订单的状态,由未支付-》待发货,客户端通过轮询、长连接,或者服务端主动推送的方式,在界面上变更订单状态。

我们再从支付流水的角度看一下支付状态的变化:

1985ca5d5bcbc6fa5f64ad19bee12c74.png
支付状态变化
  • 从未支付,到有支付结果的终态,中间还有一个中间状态支付中

  • 用户通过打开钱包--》完成支付--》支付回调,这段时间的支付流水就处于支付中

为什么要花这么多篇幅来讲支付的业务流程、交互过程呢?因为我认为,防止订单的重复支付,不止是技术上的问题,也是业务和产品上的问题。

为什么订单会重复支付

未防重导致的重复支付

我们可以看到PC端支付,是扫描二维码,这些二维码,就是对应相应的支付流水,假如用户重复点击支付,如果不做防重的的话,会生成两笔支付流水,也就是两个不同的二维码,要是用户分别扫了两个不同的支付码,那么毫无疑问,就会产生重复支付。

掉单导致的重复支付

“我明明付款了,为什么我的订单还没支付呢?”

7194c1b932c22b72bb340e8b9292233c.png
黑我钱是吧

这就是所谓的“掉单”:

  • 外部掉单:三方支付的支付状态没有同步或者没有及时同步到商城,这叫外部掉单

  • 内部掉单:支付服务的状态没有同步到订单,或者客户端没有及时获取到订单状态,这叫内部掉单。

用户一看,自己付了款,结果商城里订单还未付款,但是又特别想要,可能就会再下一单,这样就重复支付了。

多渠道导致的重复支付

我们国内支付的体验还是非常快捷的,大家可能没有感觉,如果了解过海外支付的可能了解,很多支付的渠道,消耗的时间非常长。

比如用户保罗选择了一种支付方式Boleto,结果支付的网点离保罗他们村太远了,保罗又选择了Paypal支付,保罗去赶集的时候,又顺手去网点把Boleto的这一笔支付了,结果就重复支付了。

这种情况大家可能很少遇到,我们可以用美团下一个单,先打开微信支付,不要支付啊,接着回到美团,打开支付宝,用支付宝支付完成后,用微信接着支付,大家猜猜,两笔支付是不是都能成功?答案是可以。

644e8e9149a4f4e0cdf0514f3ec29b19.png
美团多渠道支付

如何防止订单重复支付

加锁

不管是3.申请支付、还是5.支付回调,都应该以订单维度加锁,防止并发下的重复操作。

加锁,毫无疑问,也是分布式锁,通常我们会选择Redis分布式锁。

180f7d64ea7a283a10e3d5ffaeba4182.png
加锁

缓存结果

申请支付成功,支付回调成功,都应该缓存结果。

再申请支付,收到成功回调的时候,都应该先去检查支付的状态。

76fc328f937cc0ab5b577155e491f6a5.png
在这里插入图片描述

支付中流水取消

假如说,用户重复支付了,再次申请支付的时候,如果已经申请支付成功了,那么这笔支付肯定是要拒绝的。

但是,要是已经存在的这笔流水还在支付中呢?——我们不确定它是成功还是失败,肯定是不能拒绝支付的,因为可能用户支付失败了,但是状态还没同步,这样肯定是不行的。

所以,我们可以取消掉正在支付中的流水,再进行支付。

0ba1c2ea91d98be8afccbf441b96bb5e.png
支付中流水取消

已支付流水退款

现在又有新的问题了,假如发起支付的时候,有流水正在支付中,如果第三方支付平台不支持取消支付,或者用户新的支付是通过不同的渠道,我们希望尽可能提高用户的支付成功率,怎么办呢?

我们可以在发起支付的时候,订单还在支付中的情况下,允许用户发起多笔支付,在支付回调的时候,检查用户是否已经有成功流水,对后来的流水进行退款处理。

cdbdcb4b8809cd43156a0de9fb98d9db.png
支付回调

当然,退款是个很危险的操作,毕竟钱退了,可就很难追回来,一定要做好风险的控制。

主动轮询&重试防止掉单

主动轮询防止外部掉单

如果因为故障没有收到回调,或者没有及时收到回调,就可能会发生所谓的外部掉单。

防止外部掉单的关键,就在于,不能傻傻地只等三方的回调通知,而要主动去查询,用户发起支付的3s之后,就可以发起轮询了,直到拿到支付流水的最终状态,主动轮询,一般可以这么实现:

99e5b29606fc40526e4e8de7a8ad0ff3.png
轮询
  • 定时任务轮询

    使用定时任务,扫描表中支付中的流水,主动查询支付的状态,定时任务的实现方式有很多,线程池、调度框架、分布式调度框架等等。

    定时任务轮询的缺点有两个:

  1. 对数据库有一些压力,观察监控,会发现定时任务扫表的时候,有时候会造成数据库的一些“峰刺”

  2. 不便调整频率,实际上,用户发起一笔支付之后,一般都会在10s-1min中完成支付,越往后,用户完成支付,所以轮询梯度进行,会更合理一些,轮询的间隔可以设置成类似这种:3s,10s,30s,3min……

延时消息轮询

另外一种方式就是使用延时消息,用户发起支付之后,发送一个延时消息,消费到延时消息之后,查询流水支付状态,没有拿到最终状态,就再发一个延时消息。延时消息的好处是对数据库的压力没有那么大,轮询的梯度也可以进行控制,缺点是实现起来复杂一些,而且要维护消息队列。

同步+异步防止内部掉单

支付服务在收到异步通知回调、或者主动轮询到流水的最终状态后,要通知订单服务支付流水的变化,订单服务同步更新订单的状态,这个过程要尽可能保证通知成功,可以采用同步+异步的方式。

  • 同步调用:支付服务调用订单服务的通知接口,有可能会因为网络等等的原因失败,也可以重试,但是根据经验,如果网络出现一些波动,重试很可能也会失败。

  • 异步通知:支付服务还应该发送一个支付成功的消息,订单服务可以利用消息队列的重试机制,来尽可能保证支付状态的同步。

这里还有一个问题,客户端如何同步这个状态?因为可能服务端更新了订单状态,但是客户端的界面上还是未支付,得用户主动刷新一下,才能拿到最新的状态,这样明显是不太合适的。

服务端、客户端的状态同步,无非就:

  • 拉:很简单,就是客户端在用户跳回订单状态页的时候,轮询一会,如果用户完成支付,通常很短时间就能获取到状态的变更,当然这种方式对客户端的性能会有一些影响,而且很出现状态同步“漏网之鱼”的情况。

  • 推:推的实现有些麻烦,Web通常是用Websocket,对APP端的推送,一般采用第三方的推送平台。

客户端支付尽可能不外跳

不管从产品的角度,还是技术的角度,客户端发起支付这一步,其实应该尽可能地不要外跳,PC端使用支付服务生成的支付码,而不是跳转;移动端网页、APP在应用内展示支付页,当然这个是由第三方支付平台决定的。

0fef813eb0e394baf69b1e8071eddc03.png
在UC内内嵌支付宝

不知道大家留意到了没有,现在的支付宝,已经做到了不用拉起钱包,在应用内就可以完成支付,这个对于商家的意义还是比较大的,对用户体验、支付成功率,都有正面的作用,相信以国内的内卷程度,其它支付供应商,一定会“跟进”的。


好了,关于如何防止重复支付,就讲到这里。对于支付,老三也只是初窥门径,希望各位大佬不吝指教。



参考:

[1]. 服务端如何防止重复支付

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544057.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3 分钟快速上手 Spring 事件机制

小伙伴们好呀~ 今天来和大家分享下这个 Spring事件机制内容概览image-20210829132019387原理image-20210828184103069这个熟悉 观察者模式 的小伙伴应该一眼就看出来啦~其实就是个简单版的 发布-订阅模式有三个核心类👇事件 ApplicationEvent事件发布器 Application…

mis dss gis_MIS中的决策支持系统(DSS)

mis dss gisThe Decision Support System is always helpful to management people to take decisions/decisions and finds the key business insights from available information systems. 决策支持系统始终有助于管理人员做出决策/决策,并从可用的信息系统中找到…

使用Grunt构建自动化开发环境

1、准备工作 1)首页确保电脑上网,以及能够访问https://registry.npmjs.org/,因需从此网站中下载安装相应的插件; 2)电脑安装Node.js,Grunt及Grunt插件都是基于node.js运行的;如果你电脑上未装node.js&#…

面试突击66:请求转发和请求重定向有什么区别?

作者 | 磊哥来源 | Java面试真题解析(ID:aimianshi666)转载请联系授权(微信ID:GG_Stone)在 Java 中,跳转的实现方式有两种:请求转发和请求重定向,但二者是完全不同的&…

99%的Java程序员会踩的6个坑

前言作为Java程序员的你,不知道有没有踩过一些基础知识的坑。有时候,某个bug,你查了半天,最后发现竟然是一个非常低级的错误。有时候,某些代码,这一批数据功能正常,但换了一批数据就出现异常了。…

BigDecimal 的 4 个坑,你踩过几个?

背景 一直从事金融相关项目,所以对BigDecimal再熟悉不过了,也曾看到很多同学因为不知道、不了解或使用不当导致资损事件发生。所以,如果你从事金融相关项目,或者你的项目中涉及到金额的计算,那么你一定要花时间看看这篇…

Windows Server 2012 R2 里面如何安装Net Framework 3.5

图示 不要慌,和windows是不一样的,没有问题 下一步 默认即可,下一步 这里面的东西以后会装,先不管,我们今天目的是装 net framework 3.5 选一下 正在安装 如果出错了请参考: http://www.2cto.com/os/201410…

聊聊Java中代码优化的30个小技巧

今天我们一起聊聊Java中代码优化的30个小技巧,希望会对你有所帮助。1.用String.format拼接字符串不知道你有没有拼接过字符串,特别是那种有多个参数,字符串比较长的情况。比如现在有个需求:要用get请求调用第三方接口,…

面试突击69:TCP 可靠吗?为什么?

作者 | 磊哥来源 | Java面试真题解析(ID:aimianshi666)转载请联系授权(微信ID:GG_Stone)相比于 UDP 来说,TCP 的主要特性是三个:有连接、可靠、面向数据流。所谓的“有连接”指的是 …

Java 是值传递还是引用传递?

作者 | 王磊来源 | Java中文社群(ID:javacn666)转载请联系授权(微信ID:GG_Stone)开篇先来曝答案,在 Java 语言中,本质只有值传递,而无引用传递,解释和证明详见…

SpringCloud基于RocketMQ实现分布式事务

前言分布式事务是在微服务开发中经常会遇到的一个问题,之前的文章中我们已经实现了利用Seata来实现强一致性事务,其实还有一种广为人知的方案就是利用消息队列来实现分布式事务,保证数据的最终一致性,也就是我们常说的柔性事务。消…

漫画:怎么证明sleep不释放锁,而wait释放锁?

wait 加锁示例public class WaitDemo {private static Object locker new Object();public static void main(String[] args) throws InterruptedException {WaitDemo waitDemo new WaitDemo();// 启动新线程,防止主线程被休眠new Thread(() -> {try {waitDemo…

就国内某个程序员问答网站的简单的分析

为什么80%的码农都做不了架构师?>>> 一、数据抓取 分析页面数据,设计数据表结构数据只要包含投票、回答数、问题状态、最后谁回答过、浏览数、问题标题、标签,数据样例如下:由于一开只打算爬问题标题,问题…

iOS开发中 常用枚举和常用的一些运算符(易错总结)

1、色值的随机值:#define kColorValue arc4random_uniform(256)/255.0 // arc4random_uniform(256)/255.0; 求出0.0~1.0之间的数字view.backgroundColor [UIColor colorWithRed:kColorValue green: kColorValue blue: kColorValue alpha: 0.5]; 2、定时器的使用&…

明明加了唯一索引,为什么还是产生重复数据?

前段时间我踩过一个坑:在mysql8的一张innodb引擎的表中,加了唯一索引,但最后发现数据竟然还是重复了。到底怎么回事呢?本文通过一次踩坑经历,聊聊唯一索引,一些有意思的知识点。1.还原问题现场前段时间&…

nmap入门之主机发现

2019独角兽企业重金招聘Python工程师标准>>> #主机发现(HOST DISCOVERY) ##仅列出IP,不扫描 nmap -sL 192.168.70.0/24 > nmap_result.txt 2>&1##仅ping扫描,不扫描端口 nmap -sn 192.168.70.0/24##不ping扫…

面试官:为什么ConcurrentHashMap要放弃分段锁?

今天我们来讨论一下一个比较经典的面试题就是 ConcurrentHashMap 为什么放弃使用了分段锁,这个面试题阿粉相信很多人肯定觉得有点头疼,因为很少有人在开发中去研究这块的内容,今天阿粉就来给大家讲一下这个 ConcurrentHashMap 为什么在 JDK8 …

面试突击72:输入URL之后会执行什么流程?

作者 | 磊哥来源 | Java面试真题解析(ID:aimianshi666)转载请联系授权(微信ID:GG_Stone)在浏览器中输入 URL 之后,它会执行以下几个流程:执行 DNS 域名解析;封装 HTTP 请…

面试必备:TCP 经典 15 连问!

TCP协议是大厂面试必问的知识点。整理了15道非常经典的TCP面试题,希望大家都找到理想的offer呀1. 讲下TCP三次握手流程开始客户端和服务器都处于CLOSED状态,然后服务端开始监听某个端口,进入LISTEN状态第一次握手(SYN1, seqx),发…

ISP QoS Lab

ISP QoS Lab1-PQ优先级队列(PQ,Priority Queue)中,有高、中、普通、低优先级四个队列。数据包根据事先的定义放在不同的队列中,路由器按照高、中、普通、低顺序服务,只有高优先级的队列为空后才为中优先级的…