提交
This commit is contained in:
82
pxdj-java/doc/接口设计/1. 购物车的设计.md
Normal file
82
pxdj-java/doc/接口设计/1. 购物车的设计.md
Normal file
@@ -0,0 +1,82 @@
|
||||
建议阅读前,先阅读《商城表设计-购物车》相关文档
|
||||
|
||||
我们的购物车只有一个表:`tz_basket` 非常简单,但是关联了非常多的表。比如:
|
||||
|
||||
- 购物车有商品,关联商品表
|
||||
- 每个商品都有sku,关联sku表
|
||||
- 一个购物车有多个店铺的商品,关联店铺表
|
||||
- 一个购物车肯定是和用户有关的,关联用户表
|
||||
|
||||
|
||||
|
||||
我们对商品进行添加,修改,其实都很简单,最为让人难以理解的是如何将这些字段进行组合,关联满减满折等一系列的活动。
|
||||
|
||||
我们先来看下是如何获取商品信息的
|
||||
|
||||
```java
|
||||
@PostMapping("/info")
|
||||
@Operation(summary = "获取用户购物车信息" , description = "获取用户购物车信息,参数为用户选中的活动项数组,以购物车id为key")
|
||||
public R<List<ShopCartDto>> info(@RequestBody Map<Long, ShopCartParam> basketIdShopCartParamMap) {
|
||||
String userId = SecurityUtils.getUser().getUserId();
|
||||
|
||||
// 更新购物车信息,
|
||||
if (MapUtil.isNotEmpty(basketIdShopCartParamMap)) {
|
||||
basketService.updateBasketByShopCartParam(userId, basketIdShopCartParamMap);
|
||||
}
|
||||
|
||||
// 拿到购物车的所有item
|
||||
List<ShopCartItemDto> shopCartItems = basketService.getShopCartItems(userId);
|
||||
return R.ok(basketService.getShopCarts(shopCartItems));
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
这里面传了一个参数:`Map<Long, ShopCartParam> basketIdShopCartParamMap` 这里是当用户改变了某件商品的满减满折活动时,重新改变满减满折信息以后计算加个的一个方法。当然在开源是没有这个满减模块的,只有思路,具体实现需要靠自己了。
|
||||
|
||||
我们继续往下看,这里面`basketService.getShopCartItems(userId)`使用的直接是从数据库中获取的数据,而真正对满减满折、店铺等进行排列组合的,在于`basketService.getShopCarts(shopCartItems)` 这个方法。
|
||||
|
||||
|
||||
|
||||
我们进到`getShopCarts`方法内部,可以查看到一行代码`applicationContext.publishEvent(new ShopCartEvent(shopCart, shopCartItemDtoList));`,这里使用的事件的模式。这个事件的主要作用是用于对模块之间的解耦,比如我们清楚的知道当购物车需要计算价格的时候,需要满减模块的配合,进行“装饰”。最后将装饰回来的东西,返回给前端。
|
||||
|
||||
|
||||
|
||||
我们现在看看购物车返回的数据`R<List<ShopCartDto>>`,我们清楚一个购物车是分多个店铺的,每一个店铺就是一个`ShopCartDto`,我们看下这个`bean`。
|
||||
|
||||
```java
|
||||
@Data
|
||||
public class ShopCartDto implements Serializable {
|
||||
|
||||
@Schema(description = "店铺ID" , required = true)
|
||||
private Long shopId;
|
||||
|
||||
@Schema(description = "店铺名称" , required = true)
|
||||
private String shopName;
|
||||
|
||||
@Schema(description = "购物车满减活动携带的商品" , required = true)
|
||||
private List<ShopCartItemDiscountDto> shopCartItemDiscounts;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
其实一个店铺下面是有多个商品的,但是根据京东的划分,每当有满减之类的活动时,满减活动的商品总是要归到一类的,所以,每个店铺下面是多个满减活动(`List<ShopCartItemDiscountDto>`),满减活动下面是多个商品(购物项`List<ShopCartItemDto>`),到此你就能明白了`ShopCartItemDiscountDto` 里面的`ChooseDiscountItemDto` 是什么东西了,这个是选中的满减项。
|
||||
|
||||
```java
|
||||
public class ShopCartItemDiscountDto implements Serializable {
|
||||
|
||||
@Schema(description = "已选满减项" , required = true)
|
||||
private ChooseDiscountItemDto chooseDiscountItemDto;
|
||||
|
||||
@Schema(description = "商品列表" )
|
||||
private List<ShopCartItemDto> shopCartItems;
|
||||
}
|
||||
```
|
||||
|
||||
我们再留意`ShopCartItemDto` 这个`bean` ,发现还有这个东西:
|
||||
|
||||
```java
|
||||
@Schema(description = "参与满减活动列表" )
|
||||
private List<DiscountDto> discounts = new ArrayList<>();
|
||||
```
|
||||
|
||||
其实购物车的每个购物项,都是有很多个满减的活动的,可以自主选择满减活动,然后进行组合,生成新的优惠。而在这选择新的活动类型时,就需要购物车就行新的价格计算。这也就是为什么获取用户购物车信息,也就是`/info`接口需要一个这个参数的原因了`Map<Long, ShopCartParam> basketIdShopCartParamMap`
|
||||
200
pxdj-java/doc/接口设计/2. 订单设计-确认订单.md
Normal file
200
pxdj-java/doc/接口设计/2. 订单设计-确认订单.md
Normal file
@@ -0,0 +1,200 @@
|
||||
下单简单的分成几个步骤:
|
||||
|
||||
1. 用户点击“立即购买”或“购物车-结算”进入到“确认订单”页面
|
||||
2. 在“确认订单”页面选择收货地址,优惠券等,重新计算运费、订单价格
|
||||
3. 提交订单,选择支付方式进行支付
|
||||
4. 支付完毕
|
||||
|
||||
|
||||
|
||||
## 第一步:
|
||||
|
||||
1. 用户点击“立即购买”或“购物车-结算”进入到“确认订单”页面,相关url`/p/order/confirm`
|
||||
|
||||
我们希望能够有个统一下单的接口,不太希望“立即购买”和“购物车-结算”两个不同的接口影响到后面所有的流程,毕竟谁也不想一个差不多一样的接口,要写两遍,所以我们看下我们的系统是如何做的。
|
||||
|
||||
|
||||
|
||||
```java
|
||||
public class OrderParam {
|
||||
@Schema(description = "购物车id 数组" )
|
||||
private List<Long> basketIds;
|
||||
|
||||
@Schema(description = "立即购买时提交的商品项" )
|
||||
private OrderItemParam orderItem;
|
||||
}
|
||||
```
|
||||
|
||||
这里使用了两种情况:
|
||||
|
||||
- 假设`basketIds` 不为空,则说明是从购物车进入
|
||||
- 假设`orderItem` 不为空,则说明是从立即购买进入
|
||||
|
||||
通过`basketService.getShopCartItemsByOrderItems(orderParam.getBasketIds(),orderParam.getOrderItem(),userId)` 这个方法对两种情况进行组合,此时并不能将购物车商品删除,因为删除购物车中的商品,是在第三步提交订单的时候进行的,不然用户点击返回键,看到购物车里面的东西还没提交订单,东西就消失了,会感觉很奇怪。
|
||||
|
||||
|
||||
|
||||
我们重新回到`controller`层,我们看到了一行熟悉的代码`basketService.getShopCarts`
|
||||
|
||||
```java
|
||||
@PostMapping("/confirm")
|
||||
@Operation(summary = "结算,生成订单信息" , description = "传入下单所需要的参数进行下单")
|
||||
public R<ShopCartOrderMergerDto> confirm(@Valid @RequestBody OrderParam orderParam) {
|
||||
// 根据店铺组装购车中的商品信息,返回每个店铺中的购物车商品信息
|
||||
List<ShopCartDto> shopCarts = basketService.getShopCarts(shopCartItems);
|
||||
}
|
||||
```
|
||||
|
||||
这行代码我们再《购物车的设计》这篇已经着重讲过了,但是我们在这为什么还需要这个东西呢?
|
||||
|
||||
很简单,无论是点击“立即购买”或“购物车-结算”,事实上都是通过用户计算过一遍金额了,而且甚至有满减满折之类的活动,都是通过了统一的计算的。而这一套计算的流程,我们并不希望重新写一遍。所以当然是能够使用之前计算的金额,那是最好的咯。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 第二步:
|
||||
|
||||
2. 在“确认订单”页面选择收货地址,优惠券等,重新计算运费、订单价格
|
||||
|
||||
我们知道无论是在第一步还是第二步,本质上还是在确认订单的页面,其中订单页面的数据结构并没有发生任何的变化,所以其实第一步第二步是可以写在一起的。所以我们可以看到`OrderParam` 还多了两个参数
|
||||
|
||||
```java
|
||||
public class OrderParam {
|
||||
@Schema(description = "地址ID,0为默认地址" ,required=true)
|
||||
@NotNull(message = "地址不能为空")
|
||||
private Long addrId;
|
||||
|
||||
@Schema(description = "用户是否改变了优惠券的选择,如果用户改变了优惠券的选择,则完全根据传入参数进行优惠券的选择" )
|
||||
private Integer userChangeCoupon;
|
||||
|
||||
@Schema(description = "优惠券id数组" )
|
||||
private List<Long> couponIds;
|
||||
}
|
||||
```
|
||||
|
||||
但是有个问题,就是在于用户点击立即购买的时候,没有地址,那样如何计算运费呢?答案就是使用默认地址进行计算呀~
|
||||
|
||||
|
||||
|
||||
我们看下计算订单的事件,事实上有很多营销活动的时候,订单的计算也是非常的复杂的,所以我们和购物车一样,采用事件的驱动,一个接一个的对订单进行“装饰”,最后生成`ShopCartOrderMergerDto`一个合并的对象
|
||||
|
||||
```java
|
||||
@PostMapping("/confirm")
|
||||
@Operation(summary = "结算,生成订单信息" , description = "传入下单所需要的参数进行下单")
|
||||
public R<ShopCartOrderMergerDto> confirm(@Valid @RequestBody OrderParam orderParam) {
|
||||
for (ShopCartDto shopCart : shopCarts) {
|
||||
applicationContext.publishEvent(new ConfirmOrderEvent(shopCartOrder,orderParam,shopAllShopCartItems));
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
我们看下`ConfirmOrderListener` 这个事件里面的默认监听器,这里
|
||||
|
||||
|
||||
|
||||
```java
|
||||
public class ConfirmOrderListener {
|
||||
@EventListener(ConfirmOrderEvent.class)
|
||||
@Order(ConfirmOrderOrder.DEFAULT)
|
||||
public void defaultConfirmOrderEvent(ConfirmOrderEvent event) {
|
||||
|
||||
|
||||
ShopCartOrderDto shopCartOrderDto = event.getShopCartOrderDto();
|
||||
|
||||
OrderParam orderParam = event.getOrderParam();
|
||||
|
||||
String userId = SecurityUtils.getUser().getUserId();
|
||||
|
||||
// 订单的地址信息
|
||||
UserAddr userAddr = userAddrService.getUserAddrByUserId(orderParam.getAddrId(), userId);
|
||||
|
||||
double total = 0.0;
|
||||
|
||||
int totalCount = 0;
|
||||
|
||||
double transfee = 0.0;
|
||||
|
||||
for (ShopCartItemDto shopCartItem : event.getShopCartItems()) {
|
||||
// 获取商品信息
|
||||
Product product = productService.getProductByProdId(shopCartItem.getProdId());
|
||||
// 获取sku信息
|
||||
Sku sku = skuService.getSkuBySkuId(shopCartItem.getSkuId());
|
||||
if (product == null || sku == null) {
|
||||
throw new RuntimeException("购物车包含无法识别的商品");
|
||||
}
|
||||
if (product.getStatus() != 1 || sku.getStatus() != 1) {
|
||||
throw new RuntimeException("商品[" + sku.getProdName() + "]已下架");
|
||||
}
|
||||
|
||||
totalCount = shopCartItem.getProdCount() + totalCount;
|
||||
total = Arith.add(shopCartItem.getProductTotalAmount(), total);
|
||||
// 用户地址如果为空,则表示该用户从未设置过任何地址相关信息
|
||||
if (userAddr != null) {
|
||||
// 每个产品的运费相加
|
||||
transfee = Arith.add(transfee, transportManagerService.calculateTransfee(shopCartItem, userAddr));
|
||||
}
|
||||
|
||||
shopCartItem.setActualTotal(shopCartItem.getProductTotalAmount());
|
||||
shopCartOrderDto.setActualTotal(Arith.sub(total, transfee));
|
||||
shopCartOrderDto.setTotal(total);
|
||||
shopCartOrderDto.setTotalCount(totalCount);
|
||||
shopCartOrderDto.setTransfee(transfee);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
值得留意的是,有那么一行代码
|
||||
|
||||
```java
|
||||
// 用户地址如果为空,则表示该用户从未设置过任何地址相关信息
|
||||
if (userAddr != null) {
|
||||
// 每个产品的运费相加
|
||||
transfee = Arith.add(transfee, transportManagerService.calculateTransfee(shopCartItem, userAddr));
|
||||
}
|
||||
```
|
||||
运费是根据用户地址进行计算,当然还包括运费模板啦,想了解运费模板的,可以参考运费模板相关的章节。
|
||||
|
||||
那么有人就问了,那么优惠券呢?优惠券是有另一个监听器进行监听计算价格啦,购买了专业版或以上的版本就能看到源码咯~
|
||||
|
||||
|
||||
|
||||
我们看看返回给前端的订单信息:
|
||||
|
||||
```java
|
||||
@Data
|
||||
public class ShopCartOrderMergerDto implements Serializable{
|
||||
|
||||
@Schema(description = "实际总值" , required = true)
|
||||
private Double actualTotal;
|
||||
|
||||
@Schema(description = "商品总值" , required = true)
|
||||
private Double total;
|
||||
|
||||
@Schema(description = "商品总数" , required = true)
|
||||
private Integer totalCount;
|
||||
|
||||
@Schema(description = "订单优惠金额(所有店铺优惠金额相加)" , required = true)
|
||||
private Double orderReduce;
|
||||
|
||||
@Schema(description = "地址Dto" , required = true)
|
||||
private UserAddrDto userAddr;
|
||||
|
||||
@Schema(description = "每个店铺的购物车信息" , required = true)
|
||||
private List<ShopCartOrderDto> shopCartOrders;
|
||||
|
||||
@Schema(description = "整个订单可以使用的优惠券列表" , required = true)
|
||||
private List<CouponOrderDto> coupons;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
这里又有一段我们熟悉的代码:
|
||||
|
||||
```java
|
||||
@Schema(description = "每个店铺的购物车信息" , required = true)
|
||||
private List<ShopCartOrderDto> shopCartOrders;
|
||||
```
|
||||
没错这里返回的数据格式,和购物车的格式是一样的,因为第一步当中已经说明,订单来自于购物车的计算,所以会在基础上条件新的数据,基本上就是返回给前端的数据了。
|
||||
151
pxdj-java/doc/接口设计/3. 订单设计-提交订单.md
Normal file
151
pxdj-java/doc/接口设计/3. 订单设计-提交订单.md
Normal file
@@ -0,0 +1,151 @@
|
||||
> 首先我们在这里严重的批评一些,在接口订单的接口中,直接传订单金额,而不是使用下单是已经计算好金额的人,这些接口岂不是使用0.01就能将全部的商品都买下来了?
|
||||
|
||||
|
||||
|
||||
我们回到订单设计这一个模块,首先我们在确认订单的时候就已经将价格计算完成了,那么我们肯定是想将计算结果给保留下来的,至于计算的过程,我们并不希望这个过程还要进行一遍的计算。
|
||||
|
||||
|
||||
|
||||
我们返回确认订单的接口,看到这样一行代码:
|
||||
|
||||
```java
|
||||
@Operation(summary = "结算,生成订单信息" , description = "传入下单所需要的参数进行下单")
|
||||
public R<ShopCartOrderMergerDto> confirm(@Valid @RequestBody OrderParam orderParam) {
|
||||
orderService.putConfirmOrderCache(userId,shopCartOrderMergerDto);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
这里每经过一次计算,就将整个订单通过`userId`进行了保存,而这个缓存的时间为30分钟,当用户使用
|
||||
|
||||
```java
|
||||
@PostMapping("/submit")
|
||||
@Operation(summary = "提交订单,返回支付流水号" , description = "根据传入的参数判断是否为购物车提交订单,同时对购物车进行删除,用户开始进行支付")
|
||||
public R<OrderNumbersDto> submitOrders(@Valid @RequestBody SubmitOrderParam submitOrderParam) {
|
||||
ShopCartOrderMergerDto mergerOrder = orderService.getConfirmOrderCache(userId);
|
||||
if (mergerOrder == null) {
|
||||
throw new RuntimeException("订单已过期,请重新下单");
|
||||
}
|
||||
|
||||
// 省略中间一大段。。。
|
||||
|
||||
orderService.removeConfirmOrderCache(userId);
|
||||
}
|
||||
```
|
||||
|
||||
当无法获取缓存的时候告知用户订单过期,当订单进行提交完毕的时候,将之前的缓存给清除。
|
||||
|
||||
|
||||
|
||||
我们又回到提交订单中间这几行代码:
|
||||
|
||||
```java
|
||||
List<Order> orders = orderService.submit(userId,mergerOrder);
|
||||
```
|
||||
|
||||
这行代码也就是提交订单的核心代码
|
||||
|
||||
```java
|
||||
eventPublisher.publishEvent(new SubmitOrderEvent(mergerOrder, orderList));
|
||||
```
|
||||
|
||||
其中这里依旧是使用时间的方式,将订单进行提交,看下这个`SubmitOrderEvent`的默认监听事件。
|
||||
|
||||
```java
|
||||
@Component("defaultSubmitOrderListener")
|
||||
@AllArgsConstructor
|
||||
public class SubmitOrderListener {
|
||||
public void defaultSubmitOrderListener(SubmitOrderEvent event) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
这里有几段值得注意的地方:
|
||||
|
||||
- 这里是`UserAddrOrder` 并不是`UserAddr`:
|
||||
|
||||
```java
|
||||
// 把订单地址保存到数据库
|
||||
UserAddrOrder userAddrOrder = BeanUtil.copyProperties(mergerOrder.getUserAddr(), UserAddrOrder.class);
|
||||
if (userAddrOrder == null) {
|
||||
throw new RuntimeException("请填写收货地址");
|
||||
}
|
||||
userAddrOrder.setUserId(userId);
|
||||
userAddrOrder.setCreateTime(now);
|
||||
userAddrOrderService.save(userAddrOrder);
|
||||
```
|
||||
|
||||
这里是将订单的收货地址进行了保存入库的操作,这里是绝对不能只保存用户的地址id在订单中的,要将地址入库,原因是如果用户在订单中设置了一个地址,如果用户在订单还没配送的时候,将自己的地址改了的话。如果仅采用关联的地址,就会出现问题。
|
||||
|
||||
|
||||
|
||||
- 为每个店铺生成一个订单
|
||||
|
||||
```java
|
||||
// 每个店铺生成一个订单
|
||||
for (ShopCartOrderDto shopCartOrderDto : shopCartOrders) {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
这里为每个店铺创建一个订单,是为了,以后平台结算给商家时,每个商家的订单单独结算。用户确认收货时,也可以为每家店铺单独确认收货。
|
||||
|
||||
|
||||
|
||||
- 使用雪花算法生成订单id, 如果对雪花算法感兴趣的,可以去搜索下相关内容:
|
||||
|
||||
```java
|
||||
String orderNumber = String.valueOf(snowflake.nextId());
|
||||
```
|
||||
|
||||
我们不想单多台服务器生成的id冲突,也不想生成uuid这样的很奇怪的字符串id,更不想直接使用数据库主键这种东西时,雪花算法就出现咯。
|
||||
|
||||
|
||||
|
||||
- 当用户提交订单的时候,购物车里面勾选的商品,理所当然的要清空掉
|
||||
|
||||
```java
|
||||
// 删除购物车的商品信息
|
||||
if (!basketIds.isEmpty()) {
|
||||
basketMapper.deleteShopCartItemsByBasketIds(userId, basketIds);
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
- 使用数据库的乐观锁,防止超卖:
|
||||
|
||||
```java
|
||||
if (skuMapper.updateStocks(sku) == 0) {
|
||||
skuService.removeSkuCacheBySkuId(key, sku.getProdId());
|
||||
throw new RuntimeException("商品:[" + sku.getProdName() + "]库存不足");
|
||||
}
|
||||
```
|
||||
|
||||
```sql
|
||||
update tz_sku set stocks = stocks - #{sku.stocks}, version = version + 1,update_time = NOW() where sku_id = #{sku.skuId} and #{sku.stocks} <= stocks
|
||||
```
|
||||
|
||||
超卖一直是一件非常令人头疼的事情,如果对订单直接加悲观锁的话,那么下单的性能将会很差。商城最重要的就是下单啦,要是性能很差,那人家还下个鬼的单哟,所以我们采用数据库的乐观锁进行下单。
|
||||
|
||||
所谓乐观锁,就是在 where 条件下加上极限的条件,比如在这里就是更新的库存小于或等于商品的库存,在这种情况下可以对库存更新成功,则更新完成了,否则抛异常(真正的定义肯定不是这样的啦,你可以百度下 “乐观锁更新库存”)。注意这里在抛异常以前,应该将缓存也更新了,不然无法及时更新。
|
||||
|
||||
|
||||
|
||||
最后我们回到`controller`
|
||||
|
||||
```java
|
||||
return R.ok(new OrderNumbersDto(orderNumbers.toString()));
|
||||
```
|
||||
|
||||
这里面返回了多个订单项,这里就变成了并单支付咯,在多个店铺一起进行支付的时候需要进行并单支付的操作,一个店铺的时候,又要变成一个订单支付的操作,可是我们只希望有一个统一支付的接口进行调用,所以我们的支付接口要进行一点点的设计咯。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
139
pxdj-java/doc/接口设计/4. 订单设计-支付.md
Normal file
139
pxdj-java/doc/接口设计/4. 订单设计-支付.md
Normal file
@@ -0,0 +1,139 @@
|
||||
> 我们的支付时不允许在订单的支付接口传订单金额的,所以我们采用了订单号进行支付的形式
|
||||
|
||||
## 支付
|
||||
|
||||
我们来到`PayController` ,这里就是统一支付的接口,当然这里的统一支付采用的是模拟支付。
|
||||
|
||||
我们直接看一下核心代码:
|
||||
|
||||
```java
|
||||
PayInfoDto payInfo = payService.pay(userId, payParam);
|
||||
```
|
||||
|
||||
再看看里面的代码:
|
||||
|
||||
```java
|
||||
// 修改订单信息
|
||||
for (String orderNumber : orderNumbers) {
|
||||
OrderSettlement orderSettlement = new OrderSettlement();
|
||||
orderSettlement.setPayNo(payNo);
|
||||
orderSettlement.setPayType(payParam.getPayType());
|
||||
orderSettlement.setUserId(userId);
|
||||
orderSettlement.setOrderNumber(orderNumber);
|
||||
orderSettlementMapper.updateByOrderNumberAndUserId(orderSettlement);
|
||||
|
||||
Order order = orderMapper.getOrderByOrderNumber(orderNumber);
|
||||
prodName.append(order.getProdName()).append(StrUtil.COMMA);
|
||||
}
|
||||
```
|
||||
|
||||
这里对传过来的支付参数`orderNumbers`进行了拆分,为每个订单的结算信息都进行了更新,所以这里便支持了分单支付和并单支付的流程。
|
||||
|
||||
|
||||
|
||||
订单金额:
|
||||
|
||||
```java
|
||||
// 除了ordernumber不一样,其他都一样
|
||||
List<OrderSettlement> settlements = orderSettlementMapper.getSettlementsByPayNo(payNo);
|
||||
// 应支付的总金额
|
||||
double payAmount = 0.0;
|
||||
for (OrderSettlement orderSettlement : settlements) {
|
||||
payAmount = Arith.add(payAmount, orderSettlement.getPayAmount());
|
||||
}
|
||||
```
|
||||
|
||||
这里面应支付的金额是通过数据库中获取的订单金额,是不接受任何前端传入的订单金额的。
|
||||
|
||||
|
||||
|
||||
## 支付回调
|
||||
|
||||
|
||||
|
||||
我们回到`controller`
|
||||
|
||||
```java
|
||||
orderRequest.setNotifyUrl(apiConfig.getDomainName() + "/notice/pay/order");
|
||||
```
|
||||
|
||||
这里面规定的,订单回调的地址,这也就是为什么需要`api.properties` 传入`api.domainName`的原因
|
||||
|
||||
|
||||
|
||||
根据订单配置`/notice/pay/order`,我们去到订单回调的`controller`既`PayNoticeController`
|
||||
|
||||
- 验签
|
||||
|
||||
因为订单的已经决定的订单已经支付成功,所以订单的回调是需要做一些验证的。不然谁都可以调用订单回调的地址,实在是十分危险。
|
||||
|
||||
其实`wxjava`这个工具包已经对返回的参数进行了校验
|
||||
|
||||
```java
|
||||
WxPayOrderNotifyResult parseOrderNotifyResult = wxMiniPayService.parseOrderNotifyResult(xmlData);
|
||||
```
|
||||
|
||||
在上面这个方法之下,就有那么一句话
|
||||
|
||||
```java
|
||||
result.checkResult(this, this.getConfig().getSignType(), false);
|
||||
```
|
||||
|
||||
|
||||
|
||||
- 更新支付状态
|
||||
|
||||
我们看看这里的业务核心方法:
|
||||
|
||||
```java
|
||||
// 根据内部订单号更新order settlement
|
||||
payService.paySuccess(payNo, bizPayNo);
|
||||
```
|
||||
|
||||
|
||||
|
||||
```java
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<String> paySuccess(String payNo, String bizPayNo) {
|
||||
List<OrderSettlement> orderSettlements = orderSettlementMapper.selectList(new LambdaQueryWrapper<OrderSettlement>().eq(OrderSettlement::getPayNo, payNo));
|
||||
|
||||
OrderSettlement settlement = orderSettlements.get(0);
|
||||
|
||||
// 订单已支付
|
||||
if (settlement.getPayStatus() == 1) {
|
||||
log.info("订单已支付,settlement.id:{}",settlement.getSettlementId());
|
||||
return null;
|
||||
}
|
||||
// 修改订单结算信息
|
||||
if (orderSettlementMapper.updateToPay(payNo, settlement.getVersion()) < 1) {
|
||||
throw new RuntimeException("结算信息已更改");
|
||||
}
|
||||
|
||||
|
||||
List<String> orderNumbers = orderSettlements.stream().map(OrderSettlement::getOrderNumber).collect(Collectors.toList());
|
||||
|
||||
// 将订单改为已支付状态
|
||||
orderMapper.updateByToPaySuccess(orderNumbers, PayType.WECHATPAY.value());
|
||||
|
||||
List<Order> orders = orderNumbers.stream().map(orderNumber -> {
|
||||
Order order = orderMapper.getOrderByOrderNumber(orderNumber);
|
||||
order.setOrderItems(orderItemMapper.listByOrderNumber(orderNumber));
|
||||
return order;
|
||||
}).collect(Collectors.toList());
|
||||
eventPublisher.publishEvent(new PaySuccessOrderEvent(orders));
|
||||
return orderNumbers;
|
||||
}
|
||||
```
|
||||
|
||||
这里无非就是找到原来的订单,将订单变成已支付的状态。
|
||||
|
||||
|
||||
|
||||
而这里同样有事件支付成功的事件
|
||||
|
||||
```java
|
||||
eventPublisher.publishEvent(new PaySuccessOrderEvent(orders));
|
||||
```
|
||||
|
||||
这里的事件也是和营销活动有关的,比如分销,这些代码也是商业版才有的。
|
||||
3
pxdj-java/doc/接口设计/必读.md
Normal file
3
pxdj-java/doc/接口设计/必读.md
Normal file
@@ -0,0 +1,3 @@
|
||||
这里只有几点说明:
|
||||
|
||||
1. 这里写的是接口设计,如果你整个接口的接口文档,只需要启动api这个项目,然后访问 http://localhost:8086/doc.html
|
||||
Reference in New Issue
Block a user