在如今数字支付横行的时代,我们常常在购物、服务支付等场景中使用在线支付。然而,你可曾想过,背后的支付系统是如何确保你的订单不会被重复支付,保障交易安全的呢?本文将带你深入了解大厂是如何通过巧妙的设计和多层次的防护机制来避免订单重复支付的。
支付流程概览
首先,我们先来简单了解一下典型的支付流程。通常,当我们提交订单后,支付流程会经过以下关键步骤:
1,提交订单: 用户在商家平台上提交订单,生成支付请求。
2,支付网关: 订单经过支付网关,连接着第三方支付渠道(如微信、支付宝、银联等)。
3,支付中心交互: 支付中心与第三方支付渠道进行交互,完成支付。
4,支付结果通知: 支付中心异步接收支付结果,更新支付状态,并通知业务应用进行订单状态更新。
常见问题:订单掉单
在这一过程中,可能会面临一个棘手的问题,即订单掉单。无论是由于网络超时、程序错误,还是其他原因,都有可能导致支付成功但订单状态未更新,从而引发用户投诉或重复支付。
1,外部掉单和内部掉单:
a,外部掉单: 由提交订单到支付成功的过程中,可能出现超时未收到回调通知等问题。
b,内部掉单: 在支付成功后,由于支付中心或业务应用自身问题,未能及时更新订单状态。
防范措施:支付流水状态和超时处理
2,防止订单重复支付,可以采取一系列措施:
a,支付流水状态:
在支付订单中引入一个中间状态,“支付中”。在支付时,检查是否有相同订单状态为“支付中”的支付流水,使用锁确保支付的原子性。支付完成后,再将状态更新为“支付成功”。
b,超时处理:
设定支付中心的自定义超时时间,如30秒。若在规定时间内未收到支付成功回调,主动查询支付结果,以确保及时更新。可在10秒、20秒、30秒等时间点查询,超过最大查询次数则进行异常处理。
3,异步通知和接口幂等性
在支付结果通知阶段,需要注意以下两个重要方面:
异步通知:
支付中心收到支付结果后,将结果同步给业务系统。这可以通过消息队列(MQ)或直接调用实现,但直接调用需考虑重试机制,可以定义实现重试策略或借助现成框架实现如SpringBoot
Retry。
接口幂等性: 无论是支付中心还是业务应用,在接收支付结果通知时都要保证接口的幂等性,即同一消息只处理一次,忽略其余的重复通知。
4,业务应用的主动查询和超时处理
为了更全面地确保支付安全,业务应用也应主动参与:
a,超时主动查询: 发起支付时,将支付订单放入一张表中,通过定时任务定期扫描并查询支付结果。这确保即使异步通知失败,业务应用也能及时更新订单状态。
b,防止订单重复提交,可以通过业务数据唯一性生成字段,比如根据订单核心信息(单号,金额,日期,买方等核心信息)生成唯一哈希值,然后根据该唯一值创建重复提交判断,一般会创建一个分布式锁注解,通过配置索的key,在Redis中检查是否存在相同的标识,若存在则不允许重复提交,不存在则生成新的标识并设置过期时间,然后创建订单。
示例,分布式锁注解:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
String key() default "";
long timeout() default 5L;
}
AOP实现具体的锁的过程
@Aspect
@Component
@EnableAspectJAutoProxy(
exposeProxy = true
)
public class DistributedLockAspect {
private static final Logger log = LoggerFactory.getLogger(DistributedLockAspect.class);
@Autowired
private RedisUtil redisUtil;
private static RedisUtil REDIS_UTIL;
@PostConstruct
public void init() {
REDIS_UTIL = this.redisUtil;
}
public DistributedLockAspect() {
}
@Around("@annotation(com.atshuo.annotation.DistributedLock)")
public Object around(ProceedingJoinPoint point) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String ip = WebUtils.getIP(request);
MethodSignature signature = (MethodSignature)point.getSignature();
Method method = signature.getMethod();
String className = method.getDeclaringClass().getName();
String methodName = method.getName();
String methodKey = String.format("%s#%s", className, methodName);
int ipCode = MathUtils.abs(ip.hashCode());
int methodCode = MathUtils.abs(methodKey.hashCode());
String keyFmt = "%s_%d";
String key = String.format(keyFmt, ipCode, methodCode);
DistributedLock distributedLock = (DistributedLock)method.getAnnotation(DistributedLock.class);
if (!sameUrlSubmit.isIpKey()) {
key = String.format(keyFmt, "", methodCode);
}
if (StringUtils.isNotEmpty(distributedLock.key())) {
List<String> paramNameList = Arrays.asList(signature.getParameterNames());
List<Object> paramList = Arrays.asList(point.getArgs());
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext ctx = new StandardEvaluationContext();
int keyCode;
for(keyCode = 0; keyCode < paramNameList.size(); ++keyCode) {
ctx.setVariable((String)paramNameList.get(keyCode), paramList.get(keyCode));
}
keyCode = MathUtils.abs(parser.parseExpression(sameUrlSubmit.key()).getValue(ctx).toString().hashCode());
key = key + "_" + keyCode;
}
long timeout = distributedLock.timeout();
if (timeout < 0L) {
timeout = CommonConstant.TIME_TO_SUBMIT;
}
boolean result = false;
String cacheKey = "DistributedLock Key:" + key;
try {
result = REDIS_UTIL.setNX(cacheKey, UUID.randomUUID().toString(), timeout * 1000L);
} catch (Exception var19) {
if (Objects.isNull(REDIS_UTIL.get(cacheKey))) {
REDIS_UTIL.set(cacheKey, UUID.randomUUID().toString());
REDIS_UTIL.expire(cacheKey, timeout);
return true;
}
return false;
}
if (!result) {
throw new RuntimeException("请勿重复提交");
} else {
return point.proceed();
}
}
}
应用示例:在需要进行防重复提交的业务方法,增加注解,指定key和超时时间,截图如下

微信支付最佳实践
微信支付作为业界的佼佼者,提出了一些建议来优化支付系统:
1,重复提交锁:通过订单在提交支付时,先生成预支付单号,然后再发起微信支付,从而在外部实现防重复。而业务系统生成预支付单也进行防重复,即可保证整个订单支付环节的防重复支付。
2,合理设置超时时间: 根据支付业务的特点和实际情况,合理设置支付超时时间,确保用户支付体验和系统的高效运行。
3,合理使用异步通知: 合理设置异步通知的机制,保证系统可靠性和数据的一致性。
4,灵活配置支付方式: 根据不同支付方式的特点,进行灵活配置,确保系统的稳定性和安全性。

总结
通过上述措施,大型在线支付系统能够有效地防止订单重复支付,保障了用户的支付安全和交易顺利进行。从支付流水状态的控制、超时处理的设定,到异步通知和接口的幂等性保障,再到业务应用的主动查询和订单重复提交的防范,每个环节都是为了构建一个安全、高效的支付生态系统。
对于普通用户而言,这些繁琐的步骤都在幕后默默保护着我们的每一笔交易,确保我们的支付安全和便利。因此,在享受数字支付带来的便捷时,也可以更加放心地相信这些大厂背后的支付系统,它们正在用技术的力量为我们的支付保驾护航。
接下来我将给各位同学划分一张学习计划表!
学习计划
那么问题又来了,作为萌新小白,我应该先学什么,再学什么?
既然你都问的这么直白了,我就告诉你,零基础应该从什么开始学起:
阶段一:初级网络安全工程师
接下来我将给大家安排一个为期1个月的网络安全初级计划,当你学完后,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web渗透、安全服务、安全分析等岗位;其中,如果你等保模块学的好,还可以从事等保工程师。
综合薪资区间6k~15k
1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)
2、渗透测试基础(1周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等
3、操作系统基础(1周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)
4、计算机网络基础(1周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现
5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固
6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)
那么,到此为止,已经耗时1个月左右。你已经成功成为了一名“脚本小子”。那么你还想接着往下探索吗?
阶段二:中级or高级网络安全工程师(看自己能力)
综合薪资区间15k~30k
7、脚本编程学习(4周)
在网络安全领域。是否具备编程能力是“脚本小子”和真正网络安全工程师的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力。
零基础入门的同学,我建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习
搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP,IDE强烈推荐Sublime;
Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,没必要看完
用Python编写漏洞的exp,然后写一个简单的网络爬虫
PHP基本语法学习并书写一个简单的博客系统
熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选)
了解Bootstrap的布局或者CSS。
阶段三:顶级网络安全工程师
如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!
学习资料分享
当然,只给予计划不给予学习资料的行为无异于耍流氓,这里给大家整理了一份【282G】的网络安全工程师从入门到精通的学习资料包,可点击下方二维码链接领取哦。
