在如今数字支付横行的时代,我们常常在购物、服务支付等场景中使用在线支付。然而,你可曾想过,背后的支付系统是如何确保你的订单不会被重复支付,保障交易安全的呢?本文将带你深入了解大厂是如何通过巧妙的设计和多层次的防护机制来避免订单重复支付的。
支付流程概览
首先,我们先来简单了解一下典型的支付流程。通常,当我们提交订单后,支付流程会经过以下关键步骤:
1,提交订单: 用户在商家平台上提交订单,生成支付请求。
2,支付网关: 订单经过支付网关,连接着第三方支付渠道(如微信、支付宝、银联等)。
3,支付中心交互: 支付中心与第三方支付渠道进行交互,完成支付。
4,支付结果通知: 支付中心异步接收支付结果,更新支付状态,并通知业务应用进行订单状态更新。
常见问题:订单掉单
在这一过程中,可能会面临一个棘手的问题,即订单掉单。无论是由于网络超时、程序错误,还是其他原因,都有可能导致支付成功但订单状态未更新,从而引发用户投诉或重复支付。
1,外部掉单和内部掉单:
a,外部掉单: 由提交订单到支付成功的过程中,可能出现超时未收到回调通知等问题。
b,内部掉单: 在支付成功后,由于支付中心或业务应用自身问题,未能及时更新订单状态。
防范措施:支付流水状态和超时处理
b,超时处理: 设定支付中心的自定义超时时间,如30秒。若在规定时间内未收到支付成功回调,主动查询支付结果,以确保及时更新。可在10秒、20秒、30秒等时间点查询,超过最大查询次数则进行异常处理。
异步通知: 支付中心收到支付结果后,将结果同步给业务系统。这可以通过消息队列(MQ)或直接调用实现,但直接调用需考虑重试机制,可以定义实现重试策略或借助现成框架实现如SpringBoot Retry。
接口幂等性: 无论是支付中心还是业务应用,在接收支付结果通知时都要保证接口的幂等性,即同一消息只处理一次,忽略其余的重复通知。
a,超时主动查询: 发起支付时,将支付订单放入一张表中,通过定时任务定期扫描并查询支付结果。这确保即使异步通知失败,业务应用也能及时更新订单状态。
@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,灵活配置支付方式: 根据不同支付方式的特点,进行灵活配置,确保系统的稳定性和安全性。

对于普通用户而言,这些繁琐的步骤都在幕后默默保护着我们的每一笔交易,确保我们的支付安全和便利。因此,在享受数字支付带来的便捷时,也可以更加放心地相信这些大厂背后的支付系统,它们正在用技术的力量为我们的支付保驾护航。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/60812.html