学习目标
1、理解订单加锁的意义,能够复述订单可核算订单项的操作流程【总述、添加菜品、移除菜品】
2、掌握转台的前提条件并且完成转台功能开发
3、掌握为什么要根据订单生成交易单,交易单类型有那些
4、掌握发起退款的前提条件并且完成发起退款功能开发
第一章 商家平台-订单操作
1、功能区拆解
用户在下单后,对菜品数量进行调整,可告知服务员,服务员可以在商家后台进行订单操作,做订单操作需要满足下列条件
1、当前桌台的订单状态:待付款【DFK】
2、追加菜品,菜品状态为起售,且库存足够
3、退菜时,当菜品数量变0,但不会删除订单项,且退菜不可以把库存退成负数
下图为整个订单项操作的流程图,我们首先进行下流程分析:
2、功能开发
- OrderController
传入菜品、订单号、操作类型
@PostMapping("opertion-to-orderItem/{dishId}/{orderNo}/{opertionType}")
@ApiOperation(value = "操作订单菜品数量",notes = "操作订单菜品数量")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path",name = "dishId",value = "菜品ID",dataType = "Long"),
@ApiImplicitParam(paramType = "path",name = "orderNo",value = "订单编号",dataType = "Long"),
@ApiImplicitParam(paramType = "path",name = "opertionType",value = "操作动作",example = "ADD",dataType = "String")
})
public ResponseWrap<OrderVo> opertionToOrderItem(
@PathVariable("dishId") Long dishId,
@PathVariable("orderNo") Long orderNo,
@PathVariable("opertionType") String opertionType) throws ProjectException {
OrderVo orderVo = orderFace.opertionToOrderItem(dishId,orderNo,opertionType);
return ResponseWrapBuild.build(BrandEnum.SUCCEED,orderVo);
}
- OrderFace
/***
* @description 调整订单项菜品数量
* @param dishId 菜品ID
* @param orderNo 订单
* @param opertionType 操作类型
* @return
*/
OrderVo opertionToOrderItem(Long dishId, Long orderNo, String opertionType) throws ProjectException;
- OrderFaceImpl
1、判定订单待支付状态才可操作
2、判定菜品处于起售状态才可操作
3、锁定订单
4、操作订单项
4.1、添加可核算订单项
4.2、移除可核算订单项
5、计算订单总金额
6、修改订单总金额
7、返回新订单信息
@Override
@GlobalTransactional
public OrderVo opertionToOrderItem(Long dishId,
Long orderNo,
String opertionType)throws ProjectException {
//1、判定订单待支付状态才可操作
OrderVo orderVoResult = orderService.findOrderByOrderNo(orderNo);
if (!SuperConstant.DFK.equals(orderVoResult.getOrderState())){
throw new ProjectException(OrderEnum.STATUS_FAIL);
}
//2、判定菜品处于起售状态才可操作
Dish dish = dishService.getById(dishId);
if (!SuperConstant.YES.equals(dish.getEnableFlag())||
!SuperConstant.YES.equals(dish.getDishStatus())){
throw new ProjectException(OrderEnum.DISH_STATUS_FAIL);
}
//3、锁定订单
String keyOrderItem = AppletCacheConstant.ADD_TO_ORDERITEM_LOCK+orderNo;
RLock lockOrderItem = redissonClient.getLock(keyOrderItem);
try {
if (lockOrderItem.tryLock(
AppletCacheConstant.REDIS_WAIT_TIME,
AppletCacheConstant.REDIS_LEASETIME,
TimeUnit.SECONDS)){
String key = AppletCacheConstant.REPERTORY_DISH+dishId;
RAtomicLong atomicLong = redissonClient.getAtomicLong(key);
//4.1添加可核算订单项
if (opertionType.equals(SuperConstant.OPERTION_TYPE_ADD)){
this.addToOrderItem(dishId,orderNo,atomicLong);
}
//4.2、移除可核算订单项
if (opertionType.equals(SuperConstant.OPERTION_TYPE_REMOVE)){
this.removeToOrderItem(dishId,orderNo,atomicLong);
}
//5、计算订单总金额
List<OrderItem> orderItemListResult = orderItemService.findOrderItemByOrderNo(orderNo);
BigDecimal sumPrice = orderItemListResult.stream()
.map(n->{
BigDecimal price = n.getPrice();
BigDecimal reducePrice = n.getReducePrice();
Long dishNum = n.getDishNum();
//如果有优惠价格以优惠价格计算
if (EmptyUtil.isNullOrEmpty(reducePrice)){
return price.multiply(new BigDecimal(dishNum));
}else {
return reducePrice.multiply(new BigDecimal(dishNum));
}
}
).reduce(BigDecimal.ZERO, BigDecimal::add);
orderVoResult.setPayableAmountSum(sumPrice);
//6、修改订单总金额
OrderVo orderVoHandler = OrderVo.builder()
.id(orderVoResult.getId())
.payableAmountSum(sumPrice).build();
boolean flag = orderService.updateById(BeanConv.toBean(orderVoHandler, Order.class));
if (!flag){
throw new ProjectException(OrderItemEnum.SAVE_ORDER_FAIL);
}
//7、返回新订单信息
if (!EmptyUtil.isNullOrEmpty(orderVoResult)){
List<OrderItemVo> orderItemVoStatisticsList = BeanConv
.toBeanList(orderItemListResult, OrderItemVo.class);
orderVoResult.setOrderItemVoStatisticsList(orderItemVoStatisticsList);
return orderVoResult;
}
}
return orderVoResult;
} catch (InterruptedException e) {
log.error("操作订单信息异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(OrderEnum.OPERTION_SHOPPING_CART_FAIL);
}finally {
lockOrderItem.unlock();
}
}
可核算订单项-添加
1、如果库存够,redis减库存
2、修改可核算订单项
3、减菜品库存
4、减菜品库存失败,归还redis菜品库存
5、redis库存不足,虽然可以不处理,但建议还是做归还库存
/**
* @description 添加可核算订单项
* @param dishId 菜品ID
* @param orderNo 订单编号
* @param atomicLong 原子计数器
* @return
*/
private void addToOrderItem(Long dishId, Long orderNo, RAtomicLong atomicLong) throws ProjectException {
//1、如果库存够,redis减库存
if (atomicLong.decrementAndGet()>=0){
//2、修改可核算订单项
Boolean flagOrderItem = orderItemService.updateDishNum(1L, dishId,orderNo);
//3、减菜品库存
Boolean flagDish = dishService.updateDishNumber(-1L,dishId);
if (!flagOrderItem||!flagDish){
//4、减菜品库存失败,归还redis菜品库存
atomicLong.incrementAndGet();
throw new ProjectException(ShoppingCartEnum.UPDATE_DISHNUMBER_FAIL);
}
}else {
//5、redis库存不足,虽然可以不处理,但建议还是做归还库存
atomicLong.incrementAndGet();
throw new ProjectException(ShoppingCartEnum.UNDERSTOCK);
}
}
可核算订单项-移除
1、修改订单项
2、菜品库存
3、添加缓存中的库存数量
/**
* @description 移除订单项
* @param dishId 菜品ID
* @param orderNo 订单编号
* @param atomicLong 原子计数器
* @return
*/
private void removeToOrderItem(Long dishId, Long orderNo, RAtomicLong atomicLong) throws ProjectException {
//1、修改订单项
Boolean flagOrderItem = orderItemService.updateDishNum(-1L, dishId,orderNo);
//2、菜品库存
Boolean flagDish = dishService.updateDishNumber(1L,dishId);
if (!flagOrderItem||!flagDish){
throw new ProjectException(ShoppingCartEnum.UPDATE_DISHNUMBER_FAIL);
}
//3、添加缓存中的库存数量
atomicLong.incrementAndGet();
}
第二章 商家平台-转台操作
1、功能区拆解
转台功能:用户在就餐过程中,因当前桌台不符合其要求,则可寻求服务人员帮其进行换台,需要满足下列条件
1、桌台状态:当前桌台【使用中】、目标桌台【空闲中】
2、目标桌台,不能是当前桌台
3、当前桌台的账单状态:待付款【DFK】
点击【转台】:
选择【桌台】
点击【确定】
2、功能开发
- AppletController:
@PostMapping("rotary-table/{sourceTableId}/{targetTableId}/{orderNo}")
@ApiOperation(value = "转台",notes = "转台")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path",name = "sourceTableId",value = "源桌台",dataType = "Long"),
@ApiImplicitParam(paramType = "path",name = "targetTableId",value = "目标桌台",dataType = "Long"),
@ApiImplicitParam(paramType = "path",name = "orderNo",value = "订单编号",dataType = "Long")
})
public ResponseWrap<Boolean> rotaryTable(
@PathVariable("sourceTableId") Long sourceTableId,
@PathVariable("targetTableId") Long targetTableId,
@PathVariable("orderNo") Long orderNo) {
Boolean flag = appletFace.rotaryTable(sourceTableId,targetTableId,orderNo);
return ResponseWrapBuild.build(BrandEnum.SUCCEED,flag);
}
- AppletFace
/***
* @description 转台业务,满足下列条件才可以转台:
* 1、oldTableId处于USE状态
* 2、newTableId处于FREE状态
* 3、orderNo处于未付款,支付中状态
* @param sourceTableId 源桌台
* @param targetTableId 目标桌台
* @param orderNo 订单号
* @return
*/
Boolean rotaryTable(Long sourceTableId,Long targetTableId,Long orderNo) throws ProjectException;
- AppletFaceImpl
1、锁定目标桌台
2、查询目标桌台
2.1、桌台空闲
2.2、桌台非空闲
3、订单关联新桌台
4、修改桌台状态
@Override
@Transactional
public Boolean rotaryTable(Long sourceTableId, Long targetTableId, Long orderNo) throws ProjectException {
//1、锁定目标桌台
String keyTargetTableId = AppletCacheConstant.OPEN_TABLE_LOCK + targetTableId;
RLock lock = redissonClient.getLock(keyTargetTableId);
try {
Boolean flag = true;
if (lock.tryLock(
AppletCacheConstant.REDIS_WAIT_TIME,
AppletCacheConstant.REDIS_LEASETIME,
TimeUnit.SECONDS)) {
//2、查询目标桌台
Table targetTable = tableService.getById(targetTableId);
//2.1、桌台空闲
if (SuperConstant.FREE.equals(targetTable.getTableStatus())) {
//3、订单关联新桌台
flag = orderService.rotaryTable(sourceTableId, targetTableId, orderNo);
if (flag) {
//4、修改桌台状态
tableService.updateTable(TableVo.builder()
.id(targetTableId)
.tableStatus(SuperConstant.USE)
.build());
tableService.updateTable(TableVo.builder()
.id(sourceTableId)
.tableStatus(SuperConstant.FREE)
.build());
} else {
throw new ProjectException(RotaryTableEnum.ROTARY_TABLE_FAIL);
}
//2.2桌台非空闲
} else {
throw new ProjectException(RotaryTableEnum.ROTARY_TABLE_FAIL);
}
}
return flag;
}
catch (Exception e){
log.error("操作购物车详情异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(TableEnum.ROTARY_TABLE_FAIL);
}finally {
lock.unlock();
}
}
- IOrderService
/***
* @description 转台操作
* @param sourceTableId 源桌台
* @param targetTableId 目标桌台
* @param orderNo
* @return
*/
Boolean rotaryTable(Long sourceTableId, Long targetTableId, Long orderNo);
- IOrderServiceImpl
@Override
public Boolean rotaryTable(Long sourceTableId, Long targetTableId, Long orderNo) {
//查询目标桌台
Table table = tableService.getById(targetTableId);
//订单修改
Order order = Order.builder()
.tableId(table.getId())
.tableName(table.getTableName())
.areaId(table.getAreaId()).build();
LambdaQueryWrapper<Order> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(Order::getTableId,sourceTableId).eq(Order::getOrderNo,orderNo);
lambdaQueryWrapper.eq(Order::getOrderState,SuperConstant.DFK);
return update(order,lambdaQueryWrapper);
}
第三章 商家平台-发起结算、发起退款
1、发起订单结算
用户就餐完成之后,去前台进行结算,服务人员进入后天进行结算操作
1.1、功能区拆解
订单状态:
订单的状态都被数字字典维护,直接从数字字典中读取
状态 | 解释 |
---|---|
待支付 | 用户下单创建订单时 |
支付中 | 收银员发起结算,生产二维码,用户未扫码或扫码后,三方未给出最终结果前 |
支付失败 | 三方告知当前订单结算失败时 |
已结算 | 三方告知当前订单结算成功时 |
免单 | 当前订单无需支付,直接结单 |
退款【单独字段】 | 已结算后发起的线下 退款操作 |
名词 | 解释 |
---|---|
优惠 | 当前结算人员对当前订单进行优惠操作==【与员工权限有关】== |
折扣 | 当前结算人员对当前订单进行折扣操作==【与员工权限有关】== |
结算方式【结算渠道】 | 支付宝、现金、免单….. |
下图为整个订单结算的流程图,我们首先进行下流程分析:
1.2、功能开发
- OrderController
传入要结算的订单信息发起订单结算,获得结算人信息,这里我们可以从UserVoContext中直接获得当前 登录的员工==【统一权限中介绍】==
@PostMapping("handleTrading")
@ApiOperation(value = "发起订单结算",notes = "发起订单结算")
@ApiImplicitParam(name = "orderVo",value = "订单信息",dataType = "OrderVo")
public ResponseWrap<TradingVo> handleTrading(@RequestBody OrderVo orderVo){
//获得结算人信息
String userVoString = UserVoContext.getUserVoString();
UserVo userVo = JSONObject.parseObject(userVoString, UserVo.class);
orderVo.setCashierId(userVo.getId());
orderVo.setCashierName(userVo.getUsername());
TradingVo tradingVo = orderFace.handleTrading(orderVo);
return ResponseWrapBuild.build(BrandEnum.SUCCEED,tradingVo);
}
@PostMapping("handle-trading-md")
@ApiOperation(value = "订单免单",notes = "订单免单")
@ApiImplicitParam(name = "orderVo",value = "订单信息",dataType = "OrderVo")
public ResponseWrap<Boolean> handleTradingMd(@RequestBody OrderVo orderVo){
//获得当前订单结算人信息
String userVoString = UserVoContext.getUserVoString();
UserVo userVo = JSONObject.parseObject(userVoString, UserVo.class);
orderVo.setCashierId(userVo.getId());
orderVo.setCashierName(userVo.getUsername());
Boolean flag = orderFace.handleTradingMd(orderVo);
return ResponseWrapBuild.build(BrandEnum.SUCCEED,flag);
}
@PostMapping("handle-trading-gz")
@ApiOperation(value = "订单挂账",notes = "订单挂账")
@ApiImplicitParam(name = "orderVo",value = "订单信息",dataType = "OrderVo")
public ResponseWrap<Boolean> handleTradingGz(@RequestBody OrderVo orderVo){
//获得当前订单结算人信息
String userVoString = UserVoContext.getUserVoString();
UserVo userVo = JSONObject.parseObject(userVoString, UserVo.class);
orderVo.setCashierId(userVo.getId());
orderVo.setCashierName(userVo.getUsername());
Boolean flag = orderFace.handleTradingGz(orderVo);
return ResponseWrapBuild.build(BrandEnum.SUCCEED,flag);
}
- OrderFace
/***
* @description 发起订单结算
* @param orderVo 订单信息
* @return: com.itheima.restkeeper.req.TradingVo
*/
TradingVo handleTrading(OrderVo orderVo)throws ProjectException;
- OrderFaceImpl
细节因为要调用多个服务这里需要添加@GlobalTransactional【seata-at中讲解】
1、根据订单生成交易单】
2、调用支付RPC接口,进行支付
3、结算后桌台状态修改:开桌—>空闲
4、修改桌台状态
@Override
@GlobalTransactional
public TradingVo handleTrading(OrderVo orderVo) throws ProjectException{
//1、根据订单生成交易单
TradingVo tradingVo = tradingConvertor(orderVo);
if (EmptyUtil.isNullOrEmpty(tradingVo)){
throw new ProjectException(OrderEnum.FAIL);
}
//2、调用统一收单线下交易预创建
TradingVo tradingVoResult = null;
if (TradingConstant.TRADING_CHANNEL_WECHAT_PAY.equals(orderVo.getTradingChannel())||
TradingConstant.TRADING_CHANNEL_ALI_PAY.equals(orderVo.getTradingChannel())){
tradingVoResult = nativePayFace.createDownLineTrading(tradingVo);
}else if (TradingConstant.TRADING_CHANNEL_CASH_PAY.equals(orderVo.getTradingChannel())){
tradingVoResult = cashPayFace.createCachTrading(tradingVo);
}else{
throw new ProjectException(OrderEnum.PLACE_ORDER_FAIL);
}
//3、结算后桌台状态修改:开桌-->空闲
Boolean flag = true;
if (EmptyUtil.isNullOrEmpty(tradingVoResult)){
throw new ProjectException(OrderEnum.FAIL);
}else {
TableVo tableVo = TableVo.builder()
.id(orderVo.getTableId())
.tableStatus(SuperConstant.FREE).build();
//4、修改桌台状态
flag = tableFace.updateTable(tableVo);
if (!flag){
throw new ProjectException(OrderEnum.FAIL);
}
}
return tradingVoResult;
}
订单生成交易单转换
根据不同的请求动作【付款、退款、免单、挂账】,我们进行不同的交易单的构建,这里之所以要把订单转换为交易单,只要是为了==【解耦系统】==订单与业务相关,交易是一个独立的系统
/***
* @description 转换订单为交易单
* @param orderVo 订单信息
* @return: com.itheima.restkeeper.req.TradingVo
*/
private TradingVo tradingConvertor(OrderVo orderVo)throws ProjectException {
//付款动作
if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_FK)){
return payTradingVo(orderVo);
//退款动作
}else if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_TK)){
return refundTradingVo(orderVo);
//免单动作
}else if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_MD)) {
return freeChargeTradingVo(orderVo);
//挂账动作
}else if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_GZ)) {
return creditTradingVo(orderVo);
}else {
throw new ProjectException(TradingEnum.TRADING_TYPE_FAIL);
}
}
付款动作,交易单生成
/***
* @description 付款动作
* @param orderVo
* @return TradingVo 交易单
*/
private TradingVo payTradingVo(OrderVo orderVo){
Order order = orderService.getById(orderVo.getId());
//应付总金额
BigDecimal payableAmountSum = order.getPayableAmountSum();
//打折
BigDecimal discount = orderVo.getDiscount();
//优惠
BigDecimal reduce = orderVo.getReduce();
//计算实付总金额
BigDecimal realAmountSum = payableAmountSum
.multiply(discount.divide(new BigDecimal(10)))
.subtract(reduce);
//实付金额
order.setRealAmountSum(realAmountSum);
//收银人id
order.setCashierId(orderVo.getCashierId());
//收银人名称
order.setCashierName(orderVo.getCashierName());
//支付渠道
order.setTradingChannel(orderVo.getTradingChannel());
//支付类型
order.setTradingType(orderVo.getTradingType());
//订单状态:现金结算:YJS,支付宝,微信结算:FKZ
if (TradingConstant.TRADING_CHANNEL_CASH_PAY.equals(orderVo.getTradingChannel())){
order.setOrderState(TradingConstant.YJS);
}else {
order.setOrderState(TradingConstant.FKZ);
}
//防止修改分库分表分片键
order.setShardingId(null);
order.setOrderNo(null);
boolean flag = orderService.updateById(order);
//构建交易单
if (flag){
TradingVo tradingVo = TradingVo.builder()
.tradingAmount(realAmountSum)
.tradingChannel(orderVo.getTradingChannel())
.tradingType(orderVo.getTradingType())
.tradingState(order.getOrderState())
.enterpriseId(order.getEnterpriseId())
.isRefund(order.getIsRefund())
.storeId(order.getTableId())
.payeeId(orderVo.getCashierId())
.payeeName(orderVo.getCashierName())
.productOrderNo(orderVo.getOrderNo())
.memo(order.getTableName()+":"+order.getOrderNo())
.build();
return tradingVo;
}else {
throw new ProjectException(OrderEnum.UPDATE_FAIL);
}
}
退款动作,交易单生成
/***
* @description 退款动作
* @param orderVo
* @return TradingVo 交易单
*/
private TradingVo refundTradingVo(OrderVo orderVo){
Order order = orderService.getById(orderVo.getId());
//修改退款金额
order.setRefund(order.getRefund().add(orderVo.getOperTionRefund()));
//有退款行为
order.setIsRefund(SuperConstant.YES);
//防止修改分库分表分片键
order.setShardingId(null);
order.setOrderNo(null);
boolean flag = orderService.updateById(order);
//构建交易单
if (flag){
TradingVo tradingVo = TradingVo.builder()
.operTionRefund(orderVo.getOperTionRefund())//前台传递过来退款金额
.tradingAmount(order.getRealAmountSum())//订单实付金额
.refund(order.getRefund())//订单退款金额
.isRefund(SuperConstant.YES)
.tradingChannel(orderVo.getTradingChannel())
.tradingType(orderVo.getTradingType())
.enterpriseId(orderVo.getEnterpriseId())
.productOrderNo(orderVo.getOrderNo())
.memo(orderVo.getTableName()+":"+orderVo.getOrderNo())
.build();
return tradingVo;
}else {
throw new ProjectException(OrderEnum.UPDATE_FAIL);
}
}
免单动作,交易单生成
/***
* @description 免单动作
* @param orderVo
* @return TradingVo 交易单
*/
private TradingVo freeChargeTradingVo(OrderVo orderVo){
Order order = orderService.getById(orderVo.getId());
CustomerVo customerVo = customerFace.findCustomerByCustomerId(orderVo.getBuyerId());
//支付渠道
order.setTradingChannel(orderVo.getTradingChannel());
//支付类型
order.setTradingType(orderVo.getTradingType());
//订单状态:MD
order.setOrderState(TradingConstant.MD);
//收银人id
order.setCashierId(orderVo.getCashierId());
//收银人名称
order.setCashierName(orderVo.getCashierName());
//防止修改分库分表分片键
order.setShardingId(null);
order.setOrderNo(null);
//结算保存订单信息
boolean flag = orderService.updateById(order);
//构建交易单
if (flag){
TradingVo tradingVo = TradingVo.builder()
.tradingAmount(order.getPayableAmountSum())
.tradingChannel(orderVo.getTradingChannel())
.tradingType(orderVo.getTradingType())
.tradingState(order.getOrderState())
.enterpriseId(orderVo.getEnterpriseId())
.storeId(orderVo.getTableId())
.payerId(customerVo.getId())
.payerName(customerVo.getMobil())
.payeeId(orderVo.getCashierId())
.payeeName(orderVo.getCashierName())
.productOrderNo(orderVo.getOrderNo())
.memo(orderVo.getTableName()+":"+orderVo.getOrderNo())
.build();
return tradingVo;
}else {
throw new ProjectException(OrderEnum.UPDATE_FAIL);
}
}
挂账动作,交易单生成
/***
* @description 挂账动作
* @param orderVo
* @return TradingVo 交易单
*/
private TradingVo creditTradingVo(OrderVo orderVo){
Order order = orderService.getById(orderVo.getId());
CustomerVo customerVo = customerFace.findCustomerByCustomerId(orderVo.getBuyerId());
//支付渠道
order.setTradingChannel(orderVo.getTradingChannel());
//支付类型
order.setTradingType(orderVo.getTradingType());
//订单状态:MD
order.setOrderState(TradingConstant.GZ);
//收银人id
order.setCashierId(orderVo.getCashierId());
//收银人名称
order.setCashierName(orderVo.getCashierName());
//防止修改分库分表分片键
order.setShardingId(null);
order.setOrderNo(null);
//结算保存订单信息
boolean flag = orderService.updateById(order);
//构建交易单
if (flag){
TradingVo tradingVo = TradingVo.builder()
.tradingAmount(order.getPayableAmountSum())
.tradingChannel(orderVo.getTradingChannel())
.tradingType(orderVo.getTradingType())
.tradingState(order.getOrderState())
.enterpriseId(orderVo.getEnterpriseId())
.storeId(orderVo.getTableId())
.payerId(customerVo.getId())
.payerName(customerVo.getMobil())
.payeeId(orderVo.getCashierId())
.payeeName(orderVo.getCashierName())
.productOrderNo(orderVo.getOrderNo())
.memo(orderVo.getTableName()+":"+orderVo.getOrderNo())
.build();
return tradingVo;
}else {
throw new ProjectException(OrderEnum.UPDATE_FAIL);
}
}
2、发起订单退款
2.1、功能区拆解
用户订单支付完成后,发现支付金额不正确,需要收银员进行退款操作,退款的前提条件:
1、订单处于【已结算】,才可退款
2、退款金额不能超过实付款,退款的渠道只能原路返回
3、收款人与退款人必须是同一个人,如果不同不可退款
下图为整个订单退款的流程图,我们首先进行下流程分析:
2.1、功能开发
- OrderController
传入要结算的订单信息发起订单结算,获得当前订单结算人信息
@PostMapping("handle-trading-refund")
@ApiOperation(value = "订单退款",notes = "订单退款")
@ApiImplicitParam(name = "orderVo",value = "订单信息",dataType = "OrderVo")
public ResponseWrap<Boolean> handleTradingRefund(@RequestBody OrderVo orderVo){
//获得当前订单结算人信息
String userVoString = UserVoContext.getUserVoString();
UserVo userVo = JSONObject.parseObject(userVoString, UserVo.class);
orderVo.setCashierId(userVo.getId());
orderVo.setCashierName(userVo.getUsername());
Boolean flag = orderFace.handleTradingRefund(orderVo);
return ResponseWrapBuild.build(BrandEnum.SUCCEED,flag);
}
- OrderFace
/***
* @description 发起订单退款
* @param orderVo 订单信息
* @return: com.itheima.restkeeper.req.TradingVo
*/
Boolean handleTradingRefund(OrderVo orderVo)throws ProjectException;
- OrderFaceImpl
细节因为要调用多个服务这里需要添加@GlobalTransactional【seata-at中讲解】
1、获取当前交易单信息
2、退款收款人不为同一人,退款操作拒绝
3、当前交易单信息,退款操作拒绝
4、根据订单生成交易单
5、执行退款交易
@Override
@GlobalTransactional
public Boolean handleTradingRefund(OrderVo orderVo)throws ProjectException {
//1、获取当前交易单信息
OrderVo orderVoBefore = findOrderVoPaid(orderVo.getOrderNo());
//2、退款收款人不为同一人,退款操作拒绝
if (orderVo.getCashierId().longValue()!=orderVoBefore.getCashierId().longValue()){
throw new ProjectException(OrderEnum.REFUND_FAIL);
}
//3、当前交易单信息,退款操作拒绝
if (!TradingConstant.YJS.equals(orderVoBefore.getOrderState())){
throw new ProjectException(OrderEnum.REFUND_FAIL);
}
//4、根据订单生成交易单
TradingVo tradingVo = tradingConvertor(orderVo);
if (EmptyUtil.isNullOrEmpty(tradingVo)){
throw new ProjectException(OrderEnum.FAIL);
}
//5、执行统一收单交易退款接口
TradingVo tradingVoResult = nativePayFace.refundDownLineTrading(tradingVo);
if (EmptyUtil.isNullOrEmpty(tradingVoResult)){
throw new ProjectException(OrderEnum.FAIL);
}
return true;
}
订单生成交易单转换
根据不同的请求动作【付款、退款、免单、挂账】,我们进行不同的交易单的构建,这里之所以要把订单转换为交易单,只要是为了==【解耦系统】==订单与业务相关,交易是一个独立的系统
/***
* @description 转换订单为交易单
* @param orderVo 订单信息
* @return: com.itheima.restkeeper.req.TradingVo
*/
private TradingVo tradingConvertor(OrderVo orderVo)throws ProjectException {
//付款动作
if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_FK)){
return payTradingVo(orderVo);
//退款动作
}else if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_TK)){
return refundTradingVo(orderVo);
//免单动作
}else if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_MD)) {
return freeChargeTradingVo(orderVo);
//挂账动作
}else if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_GZ)) {
return creditTradingVo(orderVo);
}else {
throw new ProjectException(TradingEnum.TRADING_TYPE_FAIL);
}
}
退款渠道,交易单生成
/***
* @description 退款动作
* @param orderVo
* @return TradingVo 交易单
*/
private TradingVo refundTradingVo(OrderVo orderVo){
Order order = orderService.getById(orderVo.getId());
//修改退款金额
order.setRefund(order.getRefund().add(orderVo.getOperTionRefund()));
//有退款行为
order.setIsRefund(SuperConstant.YES);
//防止修改分库分表分片键
order.setShardingId(null);
order.setOrderNo(null);
boolean flag = orderService.updateById(order);
//构建交易单
if (flag){
TradingVo tradingVo = TradingVo.builder()
.operTionRefund(orderVo.getOperTionRefund())//前台传递过来退款金额
.tradingAmount(order.getRealAmountSum())//订单实付金额
.refund(order.getRefund())//订单退款金额
.isRefund(SuperConstant.YES)
.tradingChannel(orderVo.getTradingChannel())
.tradingType(orderVo.getTradingType())
.enterpriseId(orderVo.getEnterpriseId())
.productOrderNo(orderVo.getOrderNo())
.memo(orderVo.getTableName()+":"+orderVo.getOrderNo())
.build();
return tradingVo;
}else {
throw new ProjectException(OrderEnum.UPDATE_FAIL);
}
}
课堂讨论
1、订单操作流程的过程有那些?如何保证库存的处理?
2、转台操作流程的过程有哪些?为什么要对目标桌台加锁?
3、发起订单结算操作流程的过程有哪些?,为什么要根据订单生成交易单,而不直接用订单结算
4、发起订单退款需要注意那些细节?
5、为什么你们的项目在操作购物车的时候就扣库存呢?有没有更好的方案?
课后任务
1、完成订单操作、转台操作、发起结算、退款操作开发,同步到git【☆☆☆☆☆】
2、完成当天课堂讨论,同步到git【☆☆☆☆☆】
3、完成2-5道sql练习【☆☆☆☆】
4、梳理项目二-订单操作、转台操作、发起结算、退款操作业务,同步到git【☆☆☆】