学习目标

1、理解订单加锁的意义,能够复述订单可核算订单项的操作流程【总述、添加菜品、移除菜品】

2、掌握转台的前提条件并且完成转台功能开发

3、掌握为什么要根据订单生成交易单,交易单类型有那些

4、掌握发起退款的前提条件并且完成发起退款功能开发

第一章 商家平台-订单操作

1、功能区拆解

image.png

用户在下单后,对菜品数量进行调整,可告知服务员,服务员可以在商家后台进行订单操作,做订单操作需要满足下列条件

1、当前桌台的订单状态:待付款【DFK】

2、追加菜品,菜品状态为起售,且库存足够

3、退菜时,当菜品数量变0,但不会删除订单项,且退菜不可以把库存退成负数

下图为整个订单项操作的流程图,我们首先进行下流程分析:

image.png

2、功能开发

image.png

  • OrderController

传入菜品、订单号、操作类型

  1. @PostMapping("opertion-to-orderItem/{dishId}/{orderNo}/{opertionType}")
  2. @ApiOperation(value = "操作订单菜品数量",notes = "操作订单菜品数量")
  3. @ApiImplicitParams({
  4. @ApiImplicitParam(paramType = "path",name = "dishId",value = "菜品ID",dataType = "Long"),
  5. @ApiImplicitParam(paramType = "path",name = "orderNo",value = "订单编号",dataType = "Long"),
  6. @ApiImplicitParam(paramType = "path",name = "opertionType",value = "操作动作",example = "ADD",dataType = "String")
  7. })
  8. public ResponseWrap<OrderVo> opertionToOrderItem(
  9. @PathVariable("dishId") Long dishId,
  10. @PathVariable("orderNo") Long orderNo,
  11. @PathVariable("opertionType") String opertionType) throws ProjectException {
  12. OrderVo orderVo = orderFace.opertionToOrderItem(dishId,orderNo,opertionType);
  13. return ResponseWrapBuild.build(BrandEnum.SUCCEED,orderVo);
  14. }
  • OrderFace
  1. /***
  2. * @description 调整订单项菜品数量
  3. * @param dishId 菜品ID
  4. * @param orderNo 订单
  5. * @param opertionType 操作类型
  6. * @return
  7. */
  8. OrderVo opertionToOrderItem(Long dishId, Long orderNo, String opertionType) throws ProjectException;
  • OrderFaceImpl

image.png

1、判定订单待支付状态才可操作

2、判定菜品处于起售状态才可操作

3、锁定订单

4、操作订单项

  1. 4.1、添加可核算订单项
  2. 4.2、移除可核算订单项

5、计算订单总金额

6、修改订单总金额

7、返回新订单信息

  1. @Override
  2. @GlobalTransactional
  3. public OrderVo opertionToOrderItem(Long dishId,
  4. Long orderNo,
  5. String opertionType)throws ProjectException {
  6. //1、判定订单待支付状态才可操作
  7. OrderVo orderVoResult = orderService.findOrderByOrderNo(orderNo);
  8. if (!SuperConstant.DFK.equals(orderVoResult.getOrderState())){
  9. throw new ProjectException(OrderEnum.STATUS_FAIL);
  10. }
  11. //2、判定菜品处于起售状态才可操作
  12. Dish dish = dishService.getById(dishId);
  13. if (!SuperConstant.YES.equals(dish.getEnableFlag())||
  14. !SuperConstant.YES.equals(dish.getDishStatus())){
  15. throw new ProjectException(OrderEnum.DISH_STATUS_FAIL);
  16. }
  17. //3、锁定订单
  18. String keyOrderItem = AppletCacheConstant.ADD_TO_ORDERITEM_LOCK+orderNo;
  19. RLock lockOrderItem = redissonClient.getLock(keyOrderItem);
  20. try {
  21. if (lockOrderItem.tryLock(
  22. AppletCacheConstant.REDIS_WAIT_TIME,
  23. AppletCacheConstant.REDIS_LEASETIME,
  24. TimeUnit.SECONDS)){
  25. String key = AppletCacheConstant.REPERTORY_DISH+dishId;
  26. RAtomicLong atomicLong = redissonClient.getAtomicLong(key);
  27. //4.1添加可核算订单项
  28. if (opertionType.equals(SuperConstant.OPERTION_TYPE_ADD)){
  29. this.addToOrderItem(dishId,orderNo,atomicLong);
  30. }
  31. //4.2、移除可核算订单项
  32. if (opertionType.equals(SuperConstant.OPERTION_TYPE_REMOVE)){
  33. this.removeToOrderItem(dishId,orderNo,atomicLong);
  34. }
  35. //5、计算订单总金额
  36. List<OrderItem> orderItemListResult = orderItemService.findOrderItemByOrderNo(orderNo);
  37. BigDecimal sumPrice = orderItemListResult.stream()
  38. .map(n->{
  39. BigDecimal price = n.getPrice();
  40. BigDecimal reducePrice = n.getReducePrice();
  41. Long dishNum = n.getDishNum();
  42. //如果有优惠价格以优惠价格计算
  43. if (EmptyUtil.isNullOrEmpty(reducePrice)){
  44. return price.multiply(new BigDecimal(dishNum));
  45. }else {
  46. return reducePrice.multiply(new BigDecimal(dishNum));
  47. }
  48. }
  49. ).reduce(BigDecimal.ZERO, BigDecimal::add);
  50. orderVoResult.setPayableAmountSum(sumPrice);
  51. //6、修改订单总金额
  52. OrderVo orderVoHandler = OrderVo.builder()
  53. .id(orderVoResult.getId())
  54. .payableAmountSum(sumPrice).build();
  55. boolean flag = orderService.updateById(BeanConv.toBean(orderVoHandler, Order.class));
  56. if (!flag){
  57. throw new ProjectException(OrderItemEnum.SAVE_ORDER_FAIL);
  58. }
  59. //7、返回新订单信息
  60. if (!EmptyUtil.isNullOrEmpty(orderVoResult)){
  61. List<OrderItemVo> orderItemVoStatisticsList = BeanConv
  62. .toBeanList(orderItemListResult, OrderItemVo.class);
  63. orderVoResult.setOrderItemVoStatisticsList(orderItemVoStatisticsList);
  64. return orderVoResult;
  65. }
  66. }
  67. return orderVoResult;
  68. } catch (InterruptedException e) {
  69. log.error("操作订单信息异常:{}", ExceptionsUtil.getStackTraceAsString(e));
  70. throw new ProjectException(OrderEnum.OPERTION_SHOPPING_CART_FAIL);
  71. }finally {
  72. lockOrderItem.unlock();
  73. }
  74. }

image.png

可核算订单项-添加

1、如果库存够,redis减库存

2、修改可核算订单项

3、减菜品库存

4、减菜品库存失败,归还redis菜品库存

5、redis库存不足,虽然可以不处理,但建议还是做归还库存

  1. /**
  2. * @description 添加可核算订单项
  3. * @param dishId 菜品ID
  4. * @param orderNo 订单编号
  5. * @param atomicLong 原子计数器
  6. * @return
  7. */
  8. private void addToOrderItem(Long dishId, Long orderNo, RAtomicLong atomicLong) throws ProjectException {
  9. //1、如果库存够,redis减库存
  10. if (atomicLong.decrementAndGet()>=0){
  11. //2、修改可核算订单项
  12. Boolean flagOrderItem = orderItemService.updateDishNum(1L, dishId,orderNo);
  13. //3、减菜品库存
  14. Boolean flagDish = dishService.updateDishNumber(-1L,dishId);
  15. if (!flagOrderItem||!flagDish){
  16. //4、减菜品库存失败,归还redis菜品库存
  17. atomicLong.incrementAndGet();
  18. throw new ProjectException(ShoppingCartEnum.UPDATE_DISHNUMBER_FAIL);
  19. }
  20. }else {
  21. //5、redis库存不足,虽然可以不处理,但建议还是做归还库存
  22. atomicLong.incrementAndGet();
  23. throw new ProjectException(ShoppingCartEnum.UNDERSTOCK);
  24. }
  25. }

可核算订单项-移除

image.png

1、修改订单项

2、菜品库存

3、添加缓存中的库存数量

  1. /**
  2. * @description 移除订单项
  3. * @param dishId 菜品ID
  4. * @param orderNo 订单编号
  5. * @param atomicLong 原子计数器
  6. * @return
  7. */
  8. private void removeToOrderItem(Long dishId, Long orderNo, RAtomicLong atomicLong) throws ProjectException {
  9. //1、修改订单项
  10. Boolean flagOrderItem = orderItemService.updateDishNum(-1L, dishId,orderNo);
  11. //2、菜品库存
  12. Boolean flagDish = dishService.updateDishNumber(1L,dishId);
  13. if (!flagOrderItem||!flagDish){
  14. throw new ProjectException(ShoppingCartEnum.UPDATE_DISHNUMBER_FAIL);
  15. }
  16. //3、添加缓存中的库存数量
  17. atomicLong.incrementAndGet();
  18. }

第二章 商家平台-转台操作

1、功能区拆解

转台功能:用户在就餐过程中,因当前桌台不符合其要求,则可寻求服务人员帮其进行换台,需要满足下列条件

1、桌台状态:当前桌台【使用中】、目标桌台【空闲中】

2、目标桌台,不能是当前桌台

3、当前桌台的账单状态:待付款【DFK】

点击【转台】:

image.png

选择【桌台】

image.png

点击【确定】

image.png

2、功能开发

image.png

  • AppletController
  1. @PostMapping("rotary-table/{sourceTableId}/{targetTableId}/{orderNo}")
  2. @ApiOperation(value = "转台",notes = "转台")
  3. @ApiImplicitParams({
  4. @ApiImplicitParam(paramType = "path",name = "sourceTableId",value = "源桌台",dataType = "Long"),
  5. @ApiImplicitParam(paramType = "path",name = "targetTableId",value = "目标桌台",dataType = "Long"),
  6. @ApiImplicitParam(paramType = "path",name = "orderNo",value = "订单编号",dataType = "Long")
  7. })
  8. public ResponseWrap<Boolean> rotaryTable(
  9. @PathVariable("sourceTableId") Long sourceTableId,
  10. @PathVariable("targetTableId") Long targetTableId,
  11. @PathVariable("orderNo") Long orderNo) {
  12. Boolean flag = appletFace.rotaryTable(sourceTableId,targetTableId,orderNo);
  13. return ResponseWrapBuild.build(BrandEnum.SUCCEED,flag);
  14. }
  • AppletFace
  1. /***
  2. * @description 转台业务,满足下列条件才可以转台:
  3. * 1、oldTableId处于USE状态
  4. * 2、newTableId处于FREE状态
  5. * 3、orderNo处于未付款,支付中状态
  6. * @param sourceTableId 源桌台
  7. * @param targetTableId 目标桌台
  8. * @param orderNo 订单号
  9. * @return
  10. */
  11. Boolean rotaryTable(Long sourceTableId,Long targetTableId,Long orderNo) throws ProjectException;
  • AppletFaceImpl

image.png

1、锁定目标桌台

2、查询目标桌台

  1. 2.1、桌台空闲
  2. 2.2、桌台非空闲

3、订单关联新桌台

4、修改桌台状态

  1. @Override
  2. @Transactional
  3. public Boolean rotaryTable(Long sourceTableId, Long targetTableId, Long orderNo) throws ProjectException {
  4. //1、锁定目标桌台
  5. String keyTargetTableId = AppletCacheConstant.OPEN_TABLE_LOCK + targetTableId;
  6. RLock lock = redissonClient.getLock(keyTargetTableId);
  7. try {
  8. Boolean flag = true;
  9. if (lock.tryLock(
  10. AppletCacheConstant.REDIS_WAIT_TIME,
  11. AppletCacheConstant.REDIS_LEASETIME,
  12. TimeUnit.SECONDS)) {
  13. //2、查询目标桌台
  14. Table targetTable = tableService.getById(targetTableId);
  15. //2.1、桌台空闲
  16. if (SuperConstant.FREE.equals(targetTable.getTableStatus())) {
  17. //3、订单关联新桌台
  18. flag = orderService.rotaryTable(sourceTableId, targetTableId, orderNo);
  19. if (flag) {
  20. //4、修改桌台状态
  21. tableService.updateTable(TableVo.builder()
  22. .id(targetTableId)
  23. .tableStatus(SuperConstant.USE)
  24. .build());
  25. tableService.updateTable(TableVo.builder()
  26. .id(sourceTableId)
  27. .tableStatus(SuperConstant.FREE)
  28. .build());
  29. } else {
  30. throw new ProjectException(RotaryTableEnum.ROTARY_TABLE_FAIL);
  31. }
  32. //2.2桌台非空闲
  33. } else {
  34. throw new ProjectException(RotaryTableEnum.ROTARY_TABLE_FAIL);
  35. }
  36. }
  37. return flag;
  38. }
  39. catch (Exception e){
  40. log.error("操作购物车详情异常:{}", ExceptionsUtil.getStackTraceAsString(e));
  41. throw new ProjectException(TableEnum.ROTARY_TABLE_FAIL);
  42. }finally {
  43. lock.unlock();
  44. }
  45. }
  • IOrderService
  1. /***
  2. * @description 转台操作
  3. * @param sourceTableId 源桌台
  4. * @param targetTableId 目标桌台
  5. * @param orderNo
  6. * @return
  7. */
  8. Boolean rotaryTable(Long sourceTableId, Long targetTableId, Long orderNo);
  • IOrderServiceImpl
  1. @Override
  2. public Boolean rotaryTable(Long sourceTableId, Long targetTableId, Long orderNo) {
  3. //查询目标桌台
  4. Table table = tableService.getById(targetTableId);
  5. //订单修改
  6. Order order = Order.builder()
  7. .tableId(table.getId())
  8. .tableName(table.getTableName())
  9. .areaId(table.getAreaId()).build();
  10. LambdaQueryWrapper<Order> lambdaQueryWrapper = new LambdaQueryWrapper<>();
  11. lambdaQueryWrapper.eq(Order::getTableId,sourceTableId).eq(Order::getOrderNo,orderNo);
  12. lambdaQueryWrapper.eq(Order::getOrderState,SuperConstant.DFK);
  13. return update(order,lambdaQueryWrapper);
  14. }

第三章 商家平台-发起结算、发起退款

1、发起订单结算

用户就餐完成之后,去前台进行结算,服务人员进入后天进行结算操作

1.1、功能区拆解

image.png

image.png

订单状态:

订单的状态都被数字字典维护,直接从数字字典中读取

状态 解释
待支付 用户下单创建订单时
支付中 收银员发起结算,生产二维码,用户未扫码或扫码后,三方未给出最终结果前
支付失败 三方告知当前订单结算失败时
已结算 三方告知当前订单结算成功时
免单 当前订单无需支付,直接结单
退款【单独字段】 已结算后发起的线下
退款操作

image.png

image.png

名词 解释
优惠 当前结算人员对当前订单进行优惠操作==【与员工权限有关】==
折扣 当前结算人员对当前订单进行折扣操作==【与员工权限有关】==
结算方式【结算渠道】 支付宝、现金、免单…..

下图为整个订单结算的流程图,我们首先进行下流程分析:

image.png

1.2、功能开发

image.png

  • OrderController

传入要结算的订单信息发起订单结算,获得结算人信息,这里我们可以从UserVoContext中直接获得当前 登录的员工==【统一权限中介绍】==

  1. @PostMapping("handleTrading")
  2. @ApiOperation(value = "发起订单结算",notes = "发起订单结算")
  3. @ApiImplicitParam(name = "orderVo",value = "订单信息",dataType = "OrderVo")
  4. public ResponseWrap<TradingVo> handleTrading(@RequestBody OrderVo orderVo){
  5. //获得结算人信息
  6. String userVoString = UserVoContext.getUserVoString();
  7. UserVo userVo = JSONObject.parseObject(userVoString, UserVo.class);
  8. orderVo.setCashierId(userVo.getId());
  9. orderVo.setCashierName(userVo.getUsername());
  10. TradingVo tradingVo = orderFace.handleTrading(orderVo);
  11. return ResponseWrapBuild.build(BrandEnum.SUCCEED,tradingVo);
  12. }
  13. @PostMapping("handle-trading-md")
  14. @ApiOperation(value = "订单免单",notes = "订单免单")
  15. @ApiImplicitParam(name = "orderVo",value = "订单信息",dataType = "OrderVo")
  16. public ResponseWrap<Boolean> handleTradingMd(@RequestBody OrderVo orderVo){
  17. //获得当前订单结算人信息
  18. String userVoString = UserVoContext.getUserVoString();
  19. UserVo userVo = JSONObject.parseObject(userVoString, UserVo.class);
  20. orderVo.setCashierId(userVo.getId());
  21. orderVo.setCashierName(userVo.getUsername());
  22. Boolean flag = orderFace.handleTradingMd(orderVo);
  23. return ResponseWrapBuild.build(BrandEnum.SUCCEED,flag);
  24. }
  25. @PostMapping("handle-trading-gz")
  26. @ApiOperation(value = "订单挂账",notes = "订单挂账")
  27. @ApiImplicitParam(name = "orderVo",value = "订单信息",dataType = "OrderVo")
  28. public ResponseWrap<Boolean> handleTradingGz(@RequestBody OrderVo orderVo){
  29. //获得当前订单结算人信息
  30. String userVoString = UserVoContext.getUserVoString();
  31. UserVo userVo = JSONObject.parseObject(userVoString, UserVo.class);
  32. orderVo.setCashierId(userVo.getId());
  33. orderVo.setCashierName(userVo.getUsername());
  34. Boolean flag = orderFace.handleTradingGz(orderVo);
  35. return ResponseWrapBuild.build(BrandEnum.SUCCEED,flag);
  36. }
  • OrderFace
  1. /***
  2. * @description 发起订单结算
  3. * @param orderVo 订单信息
  4. * @return: com.itheima.restkeeper.req.TradingVo
  5. */
  6. TradingVo handleTrading(OrderVo orderVo)throws ProjectException;
  • OrderFaceImpl

细节因为要调用多个服务这里需要添加@GlobalTransactional【seata-at中讲解】

1、根据订单生成交易单】

2、调用支付RPC接口,进行支付

3、结算后桌台状态修改:开桌—>空闲

4、修改桌台状态

  1. @Override
  2. @GlobalTransactional
  3. public TradingVo handleTrading(OrderVo orderVo) throws ProjectException{
  4. //1、根据订单生成交易单
  5. TradingVo tradingVo = tradingConvertor(orderVo);
  6. if (EmptyUtil.isNullOrEmpty(tradingVo)){
  7. throw new ProjectException(OrderEnum.FAIL);
  8. }
  9. //2、调用统一收单线下交易预创建
  10. TradingVo tradingVoResult = null;
  11. if (TradingConstant.TRADING_CHANNEL_WECHAT_PAY.equals(orderVo.getTradingChannel())||
  12. TradingConstant.TRADING_CHANNEL_ALI_PAY.equals(orderVo.getTradingChannel())){
  13. tradingVoResult = nativePayFace.createDownLineTrading(tradingVo);
  14. }else if (TradingConstant.TRADING_CHANNEL_CASH_PAY.equals(orderVo.getTradingChannel())){
  15. tradingVoResult = cashPayFace.createCachTrading(tradingVo);
  16. }else{
  17. throw new ProjectException(OrderEnum.PLACE_ORDER_FAIL);
  18. }
  19. //3、结算后桌台状态修改:开桌-->空闲
  20. Boolean flag = true;
  21. if (EmptyUtil.isNullOrEmpty(tradingVoResult)){
  22. throw new ProjectException(OrderEnum.FAIL);
  23. }else {
  24. TableVo tableVo = TableVo.builder()
  25. .id(orderVo.getTableId())
  26. .tableStatus(SuperConstant.FREE).build();
  27. //4、修改桌台状态
  28. flag = tableFace.updateTable(tableVo);
  29. if (!flag){
  30. throw new ProjectException(OrderEnum.FAIL);
  31. }
  32. }
  33. return tradingVoResult;
  34. }

订单生成交易单转换

根据不同的请求动作【付款、退款、免单、挂账】,我们进行不同的交易单的构建,这里之所以要把订单转换为交易单,只要是为了==【解耦系统】==订单与业务相关,交易是一个独立的系统

  1. /***
  2. * @description 转换订单为交易单
  3. * @param orderVo 订单信息
  4. * @return: com.itheima.restkeeper.req.TradingVo
  5. */
  6. private TradingVo tradingConvertor(OrderVo orderVo)throws ProjectException {
  7. //付款动作
  8. if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_FK)){
  9. return payTradingVo(orderVo);
  10. //退款动作
  11. }else if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_TK)){
  12. return refundTradingVo(orderVo);
  13. //免单动作
  14. }else if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_MD)) {
  15. return freeChargeTradingVo(orderVo);
  16. //挂账动作
  17. }else if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_GZ)) {
  18. return creditTradingVo(orderVo);
  19. }else {
  20. throw new ProjectException(TradingEnum.TRADING_TYPE_FAIL);
  21. }
  22. }

付款动作,交易单生成

  1. /***
  2. * @description 付款动作
  3. * @param orderVo
  4. * @return TradingVo 交易单
  5. */
  6. private TradingVo payTradingVo(OrderVo orderVo){
  7. Order order = orderService.getById(orderVo.getId());
  8. //应付总金额
  9. BigDecimal payableAmountSum = order.getPayableAmountSum();
  10. //打折
  11. BigDecimal discount = orderVo.getDiscount();
  12. //优惠
  13. BigDecimal reduce = orderVo.getReduce();
  14. //计算实付总金额
  15. BigDecimal realAmountSum = payableAmountSum
  16. .multiply(discount.divide(new BigDecimal(10)))
  17. .subtract(reduce);
  18. //实付金额
  19. order.setRealAmountSum(realAmountSum);
  20. //收银人id
  21. order.setCashierId(orderVo.getCashierId());
  22. //收银人名称
  23. order.setCashierName(orderVo.getCashierName());
  24. //支付渠道
  25. order.setTradingChannel(orderVo.getTradingChannel());
  26. //支付类型
  27. order.setTradingType(orderVo.getTradingType());
  28. //订单状态:现金结算:YJS,支付宝,微信结算:FKZ
  29. if (TradingConstant.TRADING_CHANNEL_CASH_PAY.equals(orderVo.getTradingChannel())){
  30. order.setOrderState(TradingConstant.YJS);
  31. }else {
  32. order.setOrderState(TradingConstant.FKZ);
  33. }
  34. //防止修改分库分表分片键
  35. order.setShardingId(null);
  36. order.setOrderNo(null);
  37. boolean flag = orderService.updateById(order);
  38. //构建交易单
  39. if (flag){
  40. TradingVo tradingVo = TradingVo.builder()
  41. .tradingAmount(realAmountSum)
  42. .tradingChannel(orderVo.getTradingChannel())
  43. .tradingType(orderVo.getTradingType())
  44. .tradingState(order.getOrderState())
  45. .enterpriseId(order.getEnterpriseId())
  46. .isRefund(order.getIsRefund())
  47. .storeId(order.getTableId())
  48. .payeeId(orderVo.getCashierId())
  49. .payeeName(orderVo.getCashierName())
  50. .productOrderNo(orderVo.getOrderNo())
  51. .memo(order.getTableName()+":"+order.getOrderNo())
  52. .build();
  53. return tradingVo;
  54. }else {
  55. throw new ProjectException(OrderEnum.UPDATE_FAIL);
  56. }
  57. }

退款动作,交易单生成

  1. /***
  2. * @description 退款动作
  3. * @param orderVo
  4. * @return TradingVo 交易单
  5. */
  6. private TradingVo refundTradingVo(OrderVo orderVo){
  7. Order order = orderService.getById(orderVo.getId());
  8. //修改退款金额
  9. order.setRefund(order.getRefund().add(orderVo.getOperTionRefund()));
  10. //有退款行为
  11. order.setIsRefund(SuperConstant.YES);
  12. //防止修改分库分表分片键
  13. order.setShardingId(null);
  14. order.setOrderNo(null);
  15. boolean flag = orderService.updateById(order);
  16. //构建交易单
  17. if (flag){
  18. TradingVo tradingVo = TradingVo.builder()
  19. .operTionRefund(orderVo.getOperTionRefund())//前台传递过来退款金额
  20. .tradingAmount(order.getRealAmountSum())//订单实付金额
  21. .refund(order.getRefund())//订单退款金额
  22. .isRefund(SuperConstant.YES)
  23. .tradingChannel(orderVo.getTradingChannel())
  24. .tradingType(orderVo.getTradingType())
  25. .enterpriseId(orderVo.getEnterpriseId())
  26. .productOrderNo(orderVo.getOrderNo())
  27. .memo(orderVo.getTableName()+":"+orderVo.getOrderNo())
  28. .build();
  29. return tradingVo;
  30. }else {
  31. throw new ProjectException(OrderEnum.UPDATE_FAIL);
  32. }
  33. }

免单动作,交易单生成

  1. /***
  2. * @description 免单动作
  3. * @param orderVo
  4. * @return TradingVo 交易单
  5. */
  6. private TradingVo freeChargeTradingVo(OrderVo orderVo){
  7. Order order = orderService.getById(orderVo.getId());
  8. CustomerVo customerVo = customerFace.findCustomerByCustomerId(orderVo.getBuyerId());
  9. //支付渠道
  10. order.setTradingChannel(orderVo.getTradingChannel());
  11. //支付类型
  12. order.setTradingType(orderVo.getTradingType());
  13. //订单状态:MD
  14. order.setOrderState(TradingConstant.MD);
  15. //收银人id
  16. order.setCashierId(orderVo.getCashierId());
  17. //收银人名称
  18. order.setCashierName(orderVo.getCashierName());
  19. //防止修改分库分表分片键
  20. order.setShardingId(null);
  21. order.setOrderNo(null);
  22. //结算保存订单信息
  23. boolean flag = orderService.updateById(order);
  24. //构建交易单
  25. if (flag){
  26. TradingVo tradingVo = TradingVo.builder()
  27. .tradingAmount(order.getPayableAmountSum())
  28. .tradingChannel(orderVo.getTradingChannel())
  29. .tradingType(orderVo.getTradingType())
  30. .tradingState(order.getOrderState())
  31. .enterpriseId(orderVo.getEnterpriseId())
  32. .storeId(orderVo.getTableId())
  33. .payerId(customerVo.getId())
  34. .payerName(customerVo.getMobil())
  35. .payeeId(orderVo.getCashierId())
  36. .payeeName(orderVo.getCashierName())
  37. .productOrderNo(orderVo.getOrderNo())
  38. .memo(orderVo.getTableName()+":"+orderVo.getOrderNo())
  39. .build();
  40. return tradingVo;
  41. }else {
  42. throw new ProjectException(OrderEnum.UPDATE_FAIL);
  43. }
  44. }

挂账动作,交易单生成

  1. /***
  2. * @description 挂账动作
  3. * @param orderVo
  4. * @return TradingVo 交易单
  5. */
  6. private TradingVo creditTradingVo(OrderVo orderVo){
  7. Order order = orderService.getById(orderVo.getId());
  8. CustomerVo customerVo = customerFace.findCustomerByCustomerId(orderVo.getBuyerId());
  9. //支付渠道
  10. order.setTradingChannel(orderVo.getTradingChannel());
  11. //支付类型
  12. order.setTradingType(orderVo.getTradingType());
  13. //订单状态:MD
  14. order.setOrderState(TradingConstant.GZ);
  15. //收银人id
  16. order.setCashierId(orderVo.getCashierId());
  17. //收银人名称
  18. order.setCashierName(orderVo.getCashierName());
  19. //防止修改分库分表分片键
  20. order.setShardingId(null);
  21. order.setOrderNo(null);
  22. //结算保存订单信息
  23. boolean flag = orderService.updateById(order);
  24. //构建交易单
  25. if (flag){
  26. TradingVo tradingVo = TradingVo.builder()
  27. .tradingAmount(order.getPayableAmountSum())
  28. .tradingChannel(orderVo.getTradingChannel())
  29. .tradingType(orderVo.getTradingType())
  30. .tradingState(order.getOrderState())
  31. .enterpriseId(orderVo.getEnterpriseId())
  32. .storeId(orderVo.getTableId())
  33. .payerId(customerVo.getId())
  34. .payerName(customerVo.getMobil())
  35. .payeeId(orderVo.getCashierId())
  36. .payeeName(orderVo.getCashierName())
  37. .productOrderNo(orderVo.getOrderNo())
  38. .memo(orderVo.getTableName()+":"+orderVo.getOrderNo())
  39. .build();
  40. return tradingVo;
  41. }else {
  42. throw new ProjectException(OrderEnum.UPDATE_FAIL);
  43. }
  44. }

2、发起订单退款

2.1、功能区拆解

用户订单支付完成后,发现支付金额不正确,需要收银员进行退款操作,退款的前提条件:

1、订单处于【已结算】,才可退款

2、退款金额不能超过实付款,退款的渠道只能原路返回

3、收款人与退款人必须是同一个人,如果不同不可退款

image.png

image.png

下图为整个订单退款的流程图,我们首先进行下流程分析:

image.png

2.1、功能开发

image.png

  • OrderController

传入要结算的订单信息发起订单结算,获得当前订单结算人信息

  1. @PostMapping("handle-trading-refund")
  2. @ApiOperation(value = "订单退款",notes = "订单退款")
  3. @ApiImplicitParam(name = "orderVo",value = "订单信息",dataType = "OrderVo")
  4. public ResponseWrap<Boolean> handleTradingRefund(@RequestBody OrderVo orderVo){
  5. //获得当前订单结算人信息
  6. String userVoString = UserVoContext.getUserVoString();
  7. UserVo userVo = JSONObject.parseObject(userVoString, UserVo.class);
  8. orderVo.setCashierId(userVo.getId());
  9. orderVo.setCashierName(userVo.getUsername());
  10. Boolean flag = orderFace.handleTradingRefund(orderVo);
  11. return ResponseWrapBuild.build(BrandEnum.SUCCEED,flag);
  12. }
  • OrderFace
  1. /***
  2. * @description 发起订单退款
  3. * @param orderVo 订单信息
  4. * @return: com.itheima.restkeeper.req.TradingVo
  5. */
  6. Boolean handleTradingRefund(OrderVo orderVo)throws ProjectException;
  • OrderFaceImpl

细节因为要调用多个服务这里需要添加@GlobalTransactional【seata-at中讲解】

1、获取当前交易单信息

2、退款收款人不为同一人,退款操作拒绝

3、当前交易单信息,退款操作拒绝

4、根据订单生成交易单

5、执行退款交易

  1. @Override
  2. @GlobalTransactional
  3. public Boolean handleTradingRefund(OrderVo orderVo)throws ProjectException {
  4. //1、获取当前交易单信息
  5. OrderVo orderVoBefore = findOrderVoPaid(orderVo.getOrderNo());
  6. //2、退款收款人不为同一人,退款操作拒绝
  7. if (orderVo.getCashierId().longValue()!=orderVoBefore.getCashierId().longValue()){
  8. throw new ProjectException(OrderEnum.REFUND_FAIL);
  9. }
  10. //3、当前交易单信息,退款操作拒绝
  11. if (!TradingConstant.YJS.equals(orderVoBefore.getOrderState())){
  12. throw new ProjectException(OrderEnum.REFUND_FAIL);
  13. }
  14. //4、根据订单生成交易单
  15. TradingVo tradingVo = tradingConvertor(orderVo);
  16. if (EmptyUtil.isNullOrEmpty(tradingVo)){
  17. throw new ProjectException(OrderEnum.FAIL);
  18. }
  19. //5、执行统一收单交易退款接口
  20. TradingVo tradingVoResult = nativePayFace.refundDownLineTrading(tradingVo);
  21. if (EmptyUtil.isNullOrEmpty(tradingVoResult)){
  22. throw new ProjectException(OrderEnum.FAIL);
  23. }
  24. return true;
  25. }

订单生成交易单转换

根据不同的请求动作【付款、退款、免单、挂账】,我们进行不同的交易单的构建,这里之所以要把订单转换为交易单,只要是为了==【解耦系统】==订单与业务相关,交易是一个独立的系统

  1. /***
  2. * @description 转换订单为交易单
  3. * @param orderVo 订单信息
  4. * @return: com.itheima.restkeeper.req.TradingVo
  5. */
  6. private TradingVo tradingConvertor(OrderVo orderVo)throws ProjectException {
  7. //付款动作
  8. if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_FK)){
  9. return payTradingVo(orderVo);
  10. //退款动作
  11. }else if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_TK)){
  12. return refundTradingVo(orderVo);
  13. //免单动作
  14. }else if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_MD)) {
  15. return freeChargeTradingVo(orderVo);
  16. //挂账动作
  17. }else if (orderVo.getTradingType().equals(TradingConstant.TRADING_TYPE_GZ)) {
  18. return creditTradingVo(orderVo);
  19. }else {
  20. throw new ProjectException(TradingEnum.TRADING_TYPE_FAIL);
  21. }
  22. }

退款渠道,交易单生成

  1. /***
  2. * @description 退款动作
  3. * @param orderVo
  4. * @return TradingVo 交易单
  5. */
  6. private TradingVo refundTradingVo(OrderVo orderVo){
  7. Order order = orderService.getById(orderVo.getId());
  8. //修改退款金额
  9. order.setRefund(order.getRefund().add(orderVo.getOperTionRefund()));
  10. //有退款行为
  11. order.setIsRefund(SuperConstant.YES);
  12. //防止修改分库分表分片键
  13. order.setShardingId(null);
  14. order.setOrderNo(null);
  15. boolean flag = orderService.updateById(order);
  16. //构建交易单
  17. if (flag){
  18. TradingVo tradingVo = TradingVo.builder()
  19. .operTionRefund(orderVo.getOperTionRefund())//前台传递过来退款金额
  20. .tradingAmount(order.getRealAmountSum())//订单实付金额
  21. .refund(order.getRefund())//订单退款金额
  22. .isRefund(SuperConstant.YES)
  23. .tradingChannel(orderVo.getTradingChannel())
  24. .tradingType(orderVo.getTradingType())
  25. .enterpriseId(orderVo.getEnterpriseId())
  26. .productOrderNo(orderVo.getOrderNo())
  27. .memo(orderVo.getTableName()+":"+orderVo.getOrderNo())
  28. .build();
  29. return tradingVo;
  30. }else {
  31. throw new ProjectException(OrderEnum.UPDATE_FAIL);
  32. }
  33. }

课堂讨论

1、订单操作流程的过程有那些?如何保证库存的处理?

2、转台操作流程的过程有哪些?为什么要对目标桌台加锁?

3、发起订单结算操作流程的过程有哪些?,为什么要根据订单生成交易单,而不直接用订单结算

4、发起订单退款需要注意那些细节?

5、为什么你们的项目在操作购物车的时候就扣库存呢?有没有更好的方案?

课后任务

1、完成订单操作、转台操作、发起结算、退款操作开发,同步到git【☆☆☆☆☆】

2、完成当天课堂讨论,同步到git【☆☆☆☆☆】

3、完成2-5道sql练习【☆☆☆☆】

4、梳理项目二-订单操作、转台操作、发起结算、退款操作业务,同步到git【☆☆☆】