如何防止订单重复支付
想必大家对在线支付都不陌生,今天和大家聊聊如何防止订单重复支付。
看看订单支付流程
我们来看看,电商订单支付的简要流程:
订单钱包支付流程
从下单/计算开始:
-
下单/结算:这一步虽然不是直接的支付起点,但是支付相关的金额等等信息都来自结算,此时订单的状态是未支付;
-
申请支付:用户选择申请支付,客户端调用支付服务,此时在系统内产生一笔支付流水,这笔流水的状态是未支付;
-
发起支付:支付服务调用三方支付,通常这种钱包类的支付,在发起支付这一步,会响应一些支付的链接,客户端会对链接进行对应的处理;
-
钱包支付:用户进行支付,通常是通过对应的钱包进行的,大家可以回忆一下自己在购物中,支付的过程。不同的端,对钱包支付的处理是不太一样的:
京东PC端支付页
APP 端
在国内,购物大部分都是在 APP 端,产品经理会想法设法把用户带到 APP,为什么我的示例图都用京东,不用淘宝呢?因为我拿 UC 打开淘宝,会直接跳转 APP。
APP 端的钱包支付,我们应该都非常熟悉,一般是拉起钱包,支付。
APP支付
WAP 端
手机的网页站,WAP 端的支付一般是直接拉起对应的钱包,如果拉起钱包失败,就跳转界面。
京东支付 WAP端
PC 端
PC 端,通常是打开收银台,展示一个二维码,通过钱包扫码支付,下面是京东的微信支付扫码页。
-
支付回调:用户完成支付后,三方支付平台,会回调商户,通知支付结果。
-
同步订单状态:支付服务在确认支付完成后,会向订单服务同步支付的结果,订单服务变更订单的状态,由未支付 -> 待发货,客户端通过轮询、长连接,或者服务端主动推送的方式,在界面上变更订单状态。
我们再从支付流水的角度看一下支付状态的变化:
支付状态变化
-
从未支付,到有支付结果的终态,中间还有一个中间状态支付中;
-
用户通过打开钱包 -> 完成支付 -> 支付回调,这段时间的支付流水就处于支付中。
为什么要花这么多篇幅来讲支付的业务流程、交互过程呢?因为我认为,防止订单的重复支付,不止是技术上的问题,也是业务和产品上的问题。
为什么订单会重复支付
未防重导致的重复支付
我们可以看到 PC 端支付,是扫描二维码,这些二维码,就是对应相应的支付流水,假如用户重复点击支付,如果不做防重的的话,会生成两笔支付流水,也就是两个不同的二维码,要是用户分别扫了两个不同的支付码,那么毫无疑问,就会产生重复支付。
掉单导致的重复支付
“我明明付款了,为什么我的订单还没支付呢?”
黑我钱是吧
这就是所谓的“掉单”:
-
外部掉单:三方支付的支付状态没有同步或者没有及时同步到商城,这叫外部掉单;
-
内部掉单:支付服务的状态没有同步到订单,或者客户端没有及时获取到订单状态,这叫内部掉单。
用户一看,自己付了款,结果商城里订单还未付款,但是又特别想要,可能就会再下一单,这样就重复支付了。
多渠道导致的重复支付
我们国内支付的体验还是非常快捷的,大家可能没有感觉,如果了解过海外支付的可能了解,很多支付的渠道,消耗的时间非常长。
比如用户保罗选择了一种支付方式 Boleto,结果支付的网点离保罗他们村太远了,保罗又选择了 Paypal 支付。保罗去赶集的时候,又顺手去网点把 Boleto 的这一笔支付了,结果就重复支付了。
这种情况大家可能很少遇到,我们可以用美团下一个单,先打开微信支付。不要支付啊,接着回到美团,打开支付宝,用支付宝支付完成后,用微信接着支付。大家猜猜,两笔支付是不是都能成功?
答案是可以。
美团多渠道支付
如何防止订单重复支付
加锁
不管是 3.申请支付、还是 5.支付回调,都应该以订单维度加锁,防止并发下的重复操作。
加锁,毫无疑问,也是分布式锁,通常我们会选择 Redis 分布式锁。
加锁
缓存结果
申请支付成功,支付回调成功,都应该缓存结果。
再申请支付,收到成功回调的时候,都应该先去检查支付的状态。
支付中流水取消
假如说,用户重复支付了,再次申请支付的时候,如果已经申请支付成功了,那么这笔支付肯定是要拒绝的。
但是,要是已经存在的这笔流水还在支付中呢?我们不确定它是成功还是失败,肯定是不能拒绝支付的,因为可能用户支付失败了,但是状态还没同步,这样肯定是不行的。
所以,我们可以取消掉正在支付中的流水,再进行支付。
支付中流水取消
已支付流水退款
现在又有新的问题了,假如发起支付的时候,有流水正在支付中,如果第三方支付平台不支持取消支付,或者用户新的支付是通过不同的渠道,我们希望尽可能提高用户的支付成功率,怎么办呢?
我们可以在发起支付的时候,订单还在支付中的情况下,允许用户发起多笔支付,在支付回调的时候,检查用户是否已经有成功流水,对后来的流水进行退款处理。
支付回调
当然,退款是个很危险的操作,毕竟钱退了,可就很难追回来,一定要做好风险的控制。
主动轮询与重试防止掉单
如果因为故障没有收到回调,或者没有及时收到回调,就可能会发生所谓的外部掉单。
防止外部掉单的关键,就在于,不能傻傻地只等三方的回调通知,而要主动去查询,用户发起支付的 3s 之后,就可以发起轮询了,直到拿到支付流水的最终状态,主动轮询,一般可以这么实现:
轮询
1) 定时任务轮询
使用定时任务,扫描表中支付中的流水,主动查询支付的状态,定时任务的实现方式有很多,线程池、调度框架、分布式调度框架等等。
定时任务轮询的缺点有两个:
-
对数据库有一些压力,观察监控,会发现定时任务扫表的时候,有时候会造成数据库的一些“峰刺”;
-
不便调整频率,实际上,用户发起一笔支付之后,一般都会在 10s-1min 中完成支付,越往后,用户完成支付,所以轮询梯度进行,会更合理一些,轮询的间隔可以设置成类似这种:3s,10s,30s,3min 等等
2) 延时消息轮询
另外一种方式就是使用延时消息。用户发起支付之后,发送一个延时消息,消费到延时消息之后,查询流水支付状态,没有拿到最终状态,就再发一个延时消息。延时消息的好处是对数据库的压力没有那么大,轮询的梯度也可以进行控制,缺点是实现起来复杂一些,而且要维护消息队列。
同步+异步防止内部掉单
支付服务在收到异步通知回调、或者主动轮询到流水的最终状态后,要通知订单服务支付流水的变化,订单服务同步更新订单的状态,这个过程要尽可能保证通知成功,可以采用同步+异步的方式。
-
同步调用:支付服务调用订单服务的通知接口,有可能会因为网络等等的原因失败,也可以重试,但是根据经验,如果网络出现一些波动,重试很可能也会失败。
-
异步通知:支付服务还应该发送一个支付成功的消息,订单服务可以利用消息队列的重试机制,来尽可能保证支付状态的同步。
这里还有一个问题,客户端如何同步这个状态?
因为可能服务端更新了订单状态,但是客户端的界面上还是未支付,得用户主动刷新一下,才能拿到最新的状态,这样明显是不太合适的。
服务端、客户端的状态同步,无非就拉和推:
-
拉:很简单,就是客户端在用户跳回订单状态页的时候,轮询一会。如果用户完成支付,通常很短时间就能获取到状态的变更。当然,这种方式对客户端的性能会有一些影响,而且很出现状态同步“漏网之鱼”的情况。
-
推:推的实现有些麻烦,Web 通常是用 Websocket,对 APP 端的推送,一般采用第三方的推送平台。
客户端支付尽可能不外跳
不管从产品的角度,还是技术的角度,客户端发起支付这一步,其实应该尽可能地不要外跳,PC 端使用支付服务生成的支付码,而不是跳转;移动端网页、APP 在应用内展示支付页,当然这个是由第三方支付平台决定的。
在UC内内嵌支付宝
不知道大家留意到了没有,现在的支付宝,已经做到了不用拉起钱包,在应用内就可以完成支付,这个对于商家的意义还是比较大的,对用户体验、支付成功率,都有正面的作用,相信以国内的内卷程度,其它支付供应商,一定会“跟进”的。
相关文章:
如何防止订单重复支付
想必大家对在线支付都不陌生,今天和大家聊聊如何防止订单重复支付。 看看订单支付流程 我们来看看,电商订单支付的简要流程: 订单钱包支付流程 从下单/计算开始: 下单/结算:这一步虽然不是直接的支付起点,但…...
不是那么快乐的五一
大家好,我是记得诚。 五一假期结束了,明天开始上班了。 这个假期没休息好,也没出去玩。 放假前一天,接到通知让加班。 第一天就去公司加班了,属实很难受,我心想如果别人有了出远门的安排,还…...
Maven命令和配置详解
Maven命令和配置详解 1. pom基本结构2. build基本结构3. Maven命令详解3.1 打包命令3.2 常用命令3.3 批量修改版本-父子pom4. Maven配置详解4.1 settings.xml4.2 项目内的maven工程结构Maven POM构建生命周期工程实践1. pom基本结构 <?xml versi...
P3029 [USACO11NOV]Cow Lineup S 双指针 单调队列
“五一”小长假来了趟上海,在倒数第二天终于有时间做了一会儿题目,A了之后过来写一篇题解 【问题描述】 农民约翰雇一个专业摄影师给他的部分牛拍照。由于约翰的牛有好多品种,他喜欢他的照片包含每个品种的至少一头牛。 约翰的牛都站在一条沿…...
数据结构与算法之链表: Leetcode 83. 删除排序链表中的重复元素 (Typescript版)
删除排序链表中的重复元素 https://leetcode.cn/problems/remove-duplicates-from-sorted-list/ 描述 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 示例 1 输入:head [1,1,2] 输出&…...
ubuntu16.04升级到20.04后报错 By not providing “FindEigen.cmake“
编译问题: CMake Error at modules/perception/lidar/CMakeLists.txt:14 (find_package): By not providing "FindEigen.cmake" in CMAKE_MODULE_PATH this project has asked CMake to find a package configuration file provided by "Eigen&…...
设计模式——模板方法模式
是什么? 在我们的实际开发中尝尝会遇到这种问题:在设计一个系统时知道了算法所需要的关键步骤,而且确定了这些步骤的执行顺序,但是某些步骤的具体实现还不知道,或者说某些步骤的实现与具体的环境相关,例如每…...
15 | Qt的自定义信号
1 前提 Qt 5.14.2 2 具体操作 2.1 自定义信号 2.1.1 UI界面设置 2.1.1.1 widget.ui 2.1.1.2 setdialog.ui 2.1.2 headers 2.1.2.1 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE namespace Ui {class Widget; } QT_END_NAMESP…...
线性表,顺序表,链表
线性表 线性表(linear list)是n个具有相同特性的数据元素的有限序列 线性表是一种在实际中广泛使 用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的一条直线 …...
洛谷 P2782 友好城市 线性DP 最长上升子序列 二分查找 lower_bound
🍑 算法题解专栏 🍑 洛谷:友好城市 题目描述 有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。每对…...
easyexcel读取excel合并单元格数据
普通的excel列表,easyexcel读取是没有什么问题的。但是,如果有合并单元格,那么它读取的时候,能获取数据,但是数据是不完整的。如下所示的单元格数据: 我们通过简单的异步读取,最后查看数据内容&…...
2023哪款蓝牙耳机性价比高?200左右高性价比蓝牙耳机推荐
现如今的蓝牙耳机越来越多,人们在选择时不免纠结,不知道选什么蓝牙耳机比较好?针对这个问题,我来给大家推荐几款性价比高的蓝牙耳机,一起来看看吧。 一、南卡小音舱Lite2蓝牙耳机 参考价:299 蓝牙版本&am…...
Java代码弱点与修复之——Masked Field(掩码字段)
弱点描述 MF: Masked Field (FB.MF_CLASS_MASKS_FIELD) 是 FindBugs 代码分析工具的一个警告信息, 属于中风险的代码弱点。 Masked Field,翻译过来是掩码字段, 字段可以理解为属性, 那么掩码是什么意思呢? 掩码是什么? 掩码是一串二进制代码对目标字段进行位与运算,屏…...
C语言编程入门之刷题篇(C语言130题)(8)
(题目标题可以直接跳转此题链接) BC72 平均身高 描述 从键盘输入5个人的身高(米),求他们的平均身高(米)。 输入描述: 一行,连续输入5个身高(范围0.00~2.00…...
QML动画类型总结
目录 一 常用动画 二 特殊场景动画 一 常用动画 有几种类型的动画,每一种都在特定情况下都有最佳的效果,下面列出了一些常用的动画: 1、PropertyAnimation(属性动画)- 使用属性值改变播放的动画; 2、Num…...
编译一个魔兽世界开源服务端Windows需要安装什么环境
编译一个魔兽世界开源服务端Windows需要安装什么环境 大家好我是艾西,去年十月份左右wy和bx发布了在停服的公告。当时不少小伙伴都在担心如果停服了怎么办,魔兽这游戏伴随着我们渡过了太多的时光。但已经发生的事情我们只能顺其自然的等待GF的消息就好了…...
HTML5字体集合的实践经验
随着互联网的发展,网站已成为人们获取信息和交流的重要平台。而一个好的网站,不仅需要有美观的界面,还需要有良好的用户体验。其中,字体是影响用户体验的一个重要因素。下面就让我们来看看HTML字体集合的相关内容。 HTML字体集合是…...
Mybatis 框架 ( 一 ) 基本步骤
1.概念 1.1.什么是Mybatis框架 (1)Mybatis是一个半ORM(Object Relation Mapping 对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、…...
【华为OD机试真题】We Are A Team(C++javapython)100%通过率 超详细代码注释 代码优化
We Are A Team 题目描述: 总共有n个人在机房,每个人有一个标号(1<=标号<=n) ,他们分成了多个团队,需要你根据收到的m条消息判定指定的两个人是否在 一个团队中,具体的: 1、消息构成为abc,整数a、b分别代表两个人的标号,整数C代表指令 2、c = = 0 代表a和b在一…...
Oracle_Workflow_Builder工作流工具(一)
简介 目标WORKFLOW是oracle 公司的一个标准产品,它通过图形化的方式来表达业务处理过程。用户使用工作流可以灵活地定义或更改流程的结构。WORKFLOW是建立在数据库基础上的一个应用,它由后台的数据对象和前台的客户端程序组成。本文档主要介绍工作流的基…...
JavaWeb学习--RequestResponse
目录 JavaWeb学习--Request&Response 1,Request和Response的概述 request:获取请求数据 response:设置响应数据 **小结** 2,Request对象 **小结** 2.2 Request获取请求数据 **小结** 2.4 请求参数中文乱码问题 URL编码 2.5 Request请求转…...
Linux cat 命令
cat(英文全拼:concatenate)命令用于连接文件并打印到标准输出设备上。 使用权限 所有使用者 语法格式 cat [-AbeEnstTuv] [--help] [--version] fileName 参数说明: -n 或 --number:由 1 开始对所有输出的行数编…...
力扣sql中等篇练习(十四)
力扣sql中等篇练习(十四) 1 最后一个能进入电梯的人 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # 在表某一个范围内的可以考虑自连接的方式,注意连接的表只需要精准的字段 # 需要排序是因为它需要找到最后一个上车的用户 SELECT q1.person_name…...
什么是Spring FactoryBean?有什么作用?
1、什么是Spring Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IOC和AOP为内核。含有七大核心模块 2、Spring的七大模块 (1)Spring Core:核心容器提供了Spring的基本功能。核心容器的核心功能是用IOC 容器来管理类的依赖关系ÿ…...
Python List pop()方法
在Python中,列表(list)是一种有序的可变集合,可以包含任何数据类型的元素。列表对象提供了许多方法来处理列表中的元素,其中之一是pop()方法。 pop()方法用于从列表中移除并返回指定位置的元素。如果不指定位置&#…...
HJ51 输出单向链表中倒数第k个结点
写在前面: 做题环境如下: 题目渠道:牛客网 HJ51 输出单向链表中倒数第k个结点 华为机试题 编程语言:C 一、题目描述 描述 输入一个单向链表,输出该链表中倒数第k个结点,链表的倒数第1个结点为链表的尾指针…...
c#笔记-内置类型
内置类型 内置类型是一些有关键字表示的类型。关键字具有非常高的优先级,可以让你在没有别的配置的情况下, 只要用的是c#就可以使用。这也意味着这些类型是非常重要,或是基本的东西。 整数:byte, sbyte, short, ushort, int, ui…...
功能齐全的 DIY ESP32 智能手表设计之原理图讲解一
相关设计资料下载ESP32 智能手表带心率、指南针设计资料(包含Arduino源码+原理图+Gerber+3D文件).zip 目录 USB部分原理图讲解 供电部分原理图讲解 USB转串口原理图讲解...
8年测试经验分享,15K的测试工程师需要掌握那些知识?
软件测试行业是随着软件产业的发展而兴起的一个重要领域,目前处于快速发展阶段。以下是软件测试行业的现状: 人才需求增长:随着互联网、移动互联网、物联网等新技术的不断发展,软件测试人才需求呈现出快速增长的趋势。越来越多的…...
利用通信基础设施提高电网的稳态稳定性(Matlab代码实现)
目录 1 概述 2 稳态稳定性分析 2.1 系统模型 2.2 稳态稳定性 2.3 问题说明 3 仿真结果 4 Matlab代码 1 概述 随着电力系统的复杂性和规模的增加,电力系统的有效控制变得越来越困难。我们提出了一种自动控制策略,该策略基于通过通信基础设施获得的…...
wordpress文档可以下载吗/十大免费excel网站
朱熹曰:“日省其身,有则改之,无则加勉。”孔子的学生曾子也经常主张:“吾日三省吾身”今天读了一些博客,对比最近博客很久未更新、github上也一直不见项目落地,着实烦躁、厌恶自己的懒惰、每天总是好像很忙的样子&…...
php政府网站源码/搜索引擎推广排名
【实例简介】c# fastreport printdiag 修改打印机设置,弹出打印机设置【实例截图】【核心代码】using System;using System.Collections.Generic;using System.Linq;using System.Text;using FastReport;using System.Data;using System.Windows.Forms;using System…...
做网站容易挣钱吗/网络营销策划方案书范文
实现一个对整形数组的冒泡排序 #include<stdio.h> #include<stdlib.h> void bubblesort(int arr[], int size){ //数组若被传参会隐式成指针. //升序排列for (int bound 0; bound < size; bound){//此处的bound是边界,[0,bound)是已完成排序区间,[bound,size)…...
安徽网站建设公司/优秀网站网页设计
Masked AutoEncoders(MAE) Top-1准确率87.8% masked autoencoders(MAE) 是一种可扩展的计算机视觉自监督学习方法。 本文的MAE方法很简单:mask输入图像的随机patch,并重建丢失的像素 。它基于两个核心设计的。 首先…...
重庆网站seo公司/招工 最新招聘信息
1. 账户与账户安全 账户和组是操作系统的基本概念,linux的组有基本组和附加组之分,一个用户只可以加入到一个基本组中国,但是可以加入到多个附加组中.创建用户时,系统默认会自动创建同名的组,并设置用户加入该基本组中. 1.1 创建账户和组 1. useraddm-c 设置账户描述信息,一…...
ui设计的软件/宁波seo公司网站推广
各位医学方的朋友,大家好。我是Flyman!做过下游分析的小伙伴都知道富集分析的重要性,生信类文章大家总会在最后一步针对我们前面筛选出来的差异基因做一下GO/KEGG富集分析,研究一下他们参与到什么信号通路上或者参与什么生物学过程…...