Skip to content

Commit be99520

Browse files
committed
refactor:精简专栏
1 parent 02b5a3f commit be99520

5 files changed

Lines changed: 592 additions & 22 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,7 @@ docs/.vuepress/public/images 存储网站本身展示所需宣传营销图片。
5353

5454
## 3 Git GUI 工具
5555
建议下载 Github Desktop,可视化提交文章相关数据。
56-
注意本仓库分为 master、main两个分支,只在 main 分支操作文章,勿碰 master 分支!
56+
注意本仓库分为 master、main两个分支,只在 main 分支操作文章,勿碰 master 分支!
57+
58+
## FAQ
59+
文章名称不要带有括号等特色字符!

docs/.vuepress/config.js

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,6 @@ module.exports = {
9494
{text: '00-聚合支付架构', link: '/md/biz-arch/00-聚合支付架构从零到一.md'},
9595
]
9696
},
97-
{
98-
text: '交易中台',
99-
items: [
100-
{text: '00-如何防止订单二次重复支付?', link: '/md/trade/00-如何防止订单二次重复支付?.md'},
101-
]
102-
},
10397
{
10498
text: '大数据',
10599
items: [
@@ -115,22 +109,22 @@ module.exports = {
115109
]
116110
},
117111
{
118-
text: 'MQTT',
119-
items: [
120-
{text: '07-MQTT发布订阅模式介绍', link: '/md/MQTT/07-MQTT发布订阅模式介绍.md'},
121-
]
122-
},
123-
{
124-
text: '设计模式',
125-
items: [
126-
]
127-
},
128-
{
129-
text: '并发',
112+
text: 'Netty',
130113
items: [
131-
{text: '00-Java并发编程', link: '/md/concurrency/00-Java并发编程.md'},
114+
{text: '基础篇', link: '/md/netty/base/ChannelPipeline接口.md'},
132115
]
133116
},
117+
// {
118+
// text: '设计模式',
119+
// items: [
120+
// ]
121+
// },
122+
// {
123+
// text: '并发',
124+
// items: [
125+
// {text: '00-Java并发编程', link: '/md/concurrency/00-Java并发编程.md'},
126+
// ]
127+
// },
134128
{
135129
text: 'Dubbo',
136130
link: '/md/Dubbo/01-互联网架构的发展历程.md'
@@ -255,6 +249,7 @@ module.exports = {
255249
"06-运营后台系统设计.md",
256250
"07-大厂报价查询系统性能优化之道.md",
257251
"08-视频推荐索引构建.md",
252+
"09-交易中台-如何防止订单二次重复支付?.md",
258253
]
259254
}
260255
],
@@ -374,9 +369,18 @@ module.exports = {
374369
]
375370
}
376371
],
372+
"/md/netty/base": [
373+
{
374+
title: "Netty",
375+
collapsable: false,
376+
sidebarDepth: 0,
377+
children: [
378+
"ChannelPipeline接口.md"
379+
]
380+
}
381+
],
377382
}
378383
}
379384
}
380385
}
381-
};
382-
386+
};
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# 00-如何防止订单二次重复支付?
2+
3+
## 1 背景
4+
5+
用户第一次点击下单操作时,会弹出支付页面待支付。但可能存在用户在支付时发现账户金额不够,后续选择:
6+
7+
- 其他渠道支付(如微信支付转为支付宝支付)
8+
- 或采用不同终端来支付(如由电脑端支付转为app端支付)
9+
10+
这时就面临二次支付场景。
11+
12+
## 2 方案1
13+
14+
由于用户支付的时候的支付页面是html文件或是一个支付二维码,可将支付页面先存储一份在数据库中,用户二次支付时通过查询数据库来重新返回用户原来的支付页面。
15+
16+
### 2.1 缺点
17+
18+
需注意支付页面是否过期,若支付页面过期,需二次调用第三方支付
19+
20+
- 后台需维护用户第一次调用时的支付页面,增加开发成本
21+
22+
- 需要注意幂等性,即能唯一标识用户的多次请求
23+
24+
### 2.2 优点
25+
26+
规定时间内,不论用户多少次调用,后台只需要调用一次第三方支付。
27+
28+
### 2.3 流程图
29+
30+
31+
32+
![](https://javaedge.oss-cn-shanghai.aliyuncs.com/image-20240117232633314.png)
33+
34+
## 3 方案2
35+
36+
用户第二次支付的时,继续调用第三方支付,让第三方根据是否超时等情况判断是:
37+
38+
- 返回原来的支付页面
39+
- or生成一个新的支付页面返回
40+
41+
### 3.1 优点
42+
43+
便于实现,减轻自己后台下单的维护成本。【推荐】
44+
45+
46+
47+
用户二次支付时,订单微服务中存储了用户第一次下单支付的基本信息。因此第二次支付时,可通过查询第一次支付的一些基本信息来调用第三方支付。这样设计可告诉第三方支付平台这是一个订单,尤其是该订单的【剩余过期时间】。
48+
49+
### 剩余过期时间
50+
51+
后台调用第三方支付,第三方支付从收到请求信息->处理请求信息->响应请求信息是存在一定的时延的,因此一定不能死死卡住过期时间来调用第三方支付。需要预留一些时间给第三方支付处理。如支付过期时间是30分钟,当用户二次支付到达我们下单服务的时候是29分钟那么就拒绝支付。
52+
53+
### 用户超时支付的拒绝策略
54+
55+
#### 策略一
56+
57+
前端显示订单30分钟内需要支付,后端中对第三方支付实际上是31分钟内不能支付 【预留时间给后端和第三方支付交互】
58+
59+
#### 策略二
60+
61+
前端显示订单30分钟内需要支付,后端对第三方的支付实际上是当用户支付请求在地29分钟到后端就不给支付了。
62+
63+
![](https://javaedge.oss-cn-shanghai.aliyuncs.com/image-20240117233759674.png)
64+
65+
```java
66+
@Override
67+
@Transactional
68+
public JsonData repay(RepayOrderRequest repayOrderRequest) {
69+
LoginUser loginUser = LoginInterceptor.threadLocal.get();
70+
// 根据订单流水号查询第一次支付的订单信息
71+
ProductOrderDO productOrderDO = productOrderMapper.selectOne(new QueryWrapper<ProductOrderDO>().eq("out_trade_no",repayOrderRequest.getOutTradeNo()).eq("user_id",loginUser.getId()));
72+
73+
log.info("订单状态:{}",productOrderDO);
74+
75+
if(productOrderDO==null){
76+
return JsonData.buildResult(BizCodeEnum.PAY_ORDER_NOT_EXIST);
77+
}
78+
79+
// 订单状态不对,不是NEW状态
80+
if(!productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.NEW.name())){
81+
return JsonData.buildResult(BizCodeEnum.PAY_ORDER_STATE_ERROR);
82+
}else {
83+
//订单创建到现在的存活时间
84+
long orderLiveTime = CommonUtil.getCurrentTimestamp() - productOrderDO.getCreateTime().getTime();
85+
//创建订单是临界点在预留一分钟时间,比如订单实际已经存活了28分钟了,我们就对外说订单已经存活了29分钟。
86+
orderLiveTime = orderLiveTime + 60*1000;
87+
88+
//大于订单超时时间,则失效
89+
if(orderLiveTime>TimeConstant.ORDER_PAY_TIMEOUT_MILLS){
90+
return JsonData.buildResult(BizCodeEnum.PAY_ORDER_PAY_TIMEOUT);
91+
}else {
92+
93+
//记得更新DB订单支付参数 payType,还可以增加订单支付信息日志 TODO
94+
//总时间-存活的时间 = 剩下的有效时间
95+
long timeout = TimeConstant.ORDER_PAY_TIMEOUT_MILLS - orderLiveTime;
96+
//创建支付
97+
PayInfoVO payInfoVO = new PayInfoVO(productOrderDO.getOutTradeNo(),
98+
productOrderDO.getPayAmount(),repayOrderRequest.getPayType(),
99+
repayOrderRequest.getClientType(), productOrderDO.getOutTradeNo(),"",timeout);
100+
101+
log.info("payInfoVO={}",payInfoVO);
102+
//调用第三方支付
103+
String payResult = payFactory.pay(payInfoVO);
104+
if(StringUtils.isNotBlank(payResult)){
105+
log.info("创建二次支付订单成功:payInfoVO={},payResult={}",payInfoVO,payResult);
106+
return JsonData.buildSuccess(payResult);
107+
}else {
108+
log.error("创建二次支付订单失败:payInfoVO={},payResult={}",payInfoVO,payResult);
109+
return JsonData.buildResult(BizCodeEnum.PAY_ORDER_FAIL);
110+
}
111+
}
112+
}
113+
}
114+
```

0 commit comments

Comments
 (0)