## 第15章-支付成功后续业务&取消关闭订单业务
**学习目标:**
- 完成支付成功后续业务处理
- 订单状态修改
- 对接库存系统扣减库存
- 订单拆单
- 完成订单关闭后续业务处理
- 修改订单状态
- 关闭本地交易记录
- 关闭支付宝交易记录
# 1、支付成功处理
## 1.1 更改订单状态
订单支付成功后,我们已经更改了订单支付记录状态,接下来我还有**更改订单状态**,因为他们是不同的微服务模块,所以我们采用**消息队列**的方式,保证**数据最终一致性**;
### 1.1.1 在MqConst常量类添加变量
```java
/**
* 订单支付
*/
public static final String EXCHANGE_DIRECT_PAYMENT_PAY = "exchange.direct.payment.pay";
public static final String ROUTING_PAYMENT_PAY = "payment.pay";
//队列
public static final String QUEUE_PAYMENT_PAY = "queue.payment.pay";
/**
* 减库存
*/
public static final String EXCHANGE_DIRECT_WARE_STOCK = "exchange.direct.ware.stock";
public static final String ROUTING_WARE_STOCK = "ware.stock";
//队列
public static final String QUEUE_WARE_STOCK = "queue.ware.stock";
/**
* 减库存成功,更新订单状态
*/
public static final String EXCHANGE_DIRECT_WARE_ORDER = "exchange.direct.ware.order";
public static final String ROUTING_WARE_ORDER = "ware.order";
//队列
public static final String QUEUE_WARE_ORDER = "queue.ware.order";
```
### 1.1.2 在service-payment 中添加依赖和配置
```xml
com.atguigu.gmall
rabbit-util
1.0
```
### 1.1.3 支付成功发送消息
`service-payment`模块com.atguigu.gmall.payment.service.impl.PaymentInfoServiceImpl#paySuccess方法
```java
@Autowired
private RabbitService rabbitService;
/**
* 修改本地交易记录状态
*
* @param outTradeNo 订单编号
* @param paymentType 支付方式
* @param paramsMap 支付宝回调参数
*/
@Override
public void paySuccess(String outTradeNo, String paymentType, Map paramsMap) {
//1.修改本地支付表状态为已支付
LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(PaymentInfo::getOutTradeNo, outTradeNo);
updateWrapper.eq(PaymentInfo::getPaymentType, paymentType);
updateWrapper.set(PaymentInfo::getPaymentStatus, PaymentStatus.PAID.name());
updateWrapper.set(PaymentInfo::getTradeNo, paramsMap.get("trade_no"));
updateWrapper.set(PaymentInfo::getCallbackTime, new Date());
updateWrapper.set(PaymentInfo::getCallbackContent, paramsMap.toString());
this.update(updateWrapper);
//2.todo 新增代码 发送修改订单状态消息到MQ 通知 订单微服务
PaymentInfo paymentInfo = this.getPaymentInfo(outTradeNo, paymentType);
rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_PAYMENT_PAY, MqConst.ROUTING_PAYMENT_PAY, paymentInfo.getOrderId());
}
```
### 1.1.4 service-order模块接收消息
`service-order`模块中创建OrderReceiver类添加方法
```java
package com.atguigu.gmall.order.receiver;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.model.enums.ProcessStatus;
import com.atguigu.gmall.model.order.OrderInfo;
import com.atguigu.gmall.order.service.OrderService;
import com.rabbitmq.client.Channel;
import lombok.SneakyThrows;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
**/
@Component
public class OrderReceiver {
@Autowired
private OrderInfoService orderInfoService;
/**
* 监听到支付成功后消息,修改订单状态为已支付;发送扣减库存消息到MQ
*
* @param orderId
* @param message
* @param channel
*/
@SneakyThrows
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange(MqConst.EXCHANGE_DIRECT_PAYMENT_PAY),
value = @Queue(MqConst.QUEUE_PAYMENT_PAY),
key = {MqConst.ROUTING_PAYMENT_PAY}
))
public void paySuccessUpdateOrder(Long orderId, Message message, Channel channel) {
try {
// 判断订单Id
if (orderId != null) {
log.info("【订单微服务】监听到支付成功的订单:ID为:{}", orderId);
OrderInfo orderInfo = orderInfoService.getById(orderId);
//通过业务字段判消息幂等性.
if (orderInfo != null && !OrderStatus.PAID.name().equals(orderInfo.getOrderStatus())) {
//更新订单
orderInfoService.updateOrderStatus(orderId, ProcessStatus.PAID);
}
}
} catch (Exception e) {
e.printStackTrace();
}
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
```
## 1.2 订单模块发送减库存通知
订单模块除了接收到请求改变单据状态,还要发送库存系统
查看看《库存管理系统接口手册》中【减库存的消息队列消费端接口】中的描述,组织相应的消息数据进行传递。
![](assets/image-20230127165931001.png)
### 1.1.1 OrderService接口
```java
/**
* 发送扣减库存消息到MQ 通知 仓库系统
* 消息格式JSON字符串包含什么数据?查询订单订单明细
*
* @param orderId
*/
void sendDeductStockMsg(Long orderId);
/**
* 将订单订单明细转为Map 后续该方法被复用
* @param orderInfo
* @return
*/
Map initWareOrder(OrderInfo orderInfo);
```
### 1.1.2 编写实现类
```java
/**
* 发送扣减库存消息到MQ 通知 仓库系统
* 消息格式JSON字符串包含什么数据?查询订单订单明细
*
* @param orderId
*/
@Override
public void sendDeductStockMsg(Long orderId) {
//1.根据订单ID查询订单详情跟订单明细(商品)
OrderInfo orderInfo = this.getOrderInfo(orderId);
//2.将得到消息对应java对象转为Map
Map wareMap = initWareOrder(orderInfo);
//3.发送消息到RabbitMQ
rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_WARE_STOCK, MqConst.ROUTING_WARE_STOCK, JSON.toJSONString(wareMap));
}
/**
* 将得到订单以及明细转为Map
*
* @param orderInfo
* @return
*/
@Override
public Map initWareOrder(OrderInfo orderInfo) {
HashMap mapResult = new HashMap<>();
//从orderInfo获取封装相关订单信息
mapResult.put("orderId", orderInfo.getId());
mapResult.put("consignee", orderInfo.getConsignee());
mapResult.put("consigneeTel", orderInfo.getConsigneeTel());
mapResult.put("orderComment", orderInfo.getOrderComment());
mapResult.put("orderBody", orderInfo.getTradeBody());
mapResult.put("deliveryAddress", orderInfo.getDeliveryAddress());
mapResult.put("paymentWay", "2");
//TODO 拆单响应结果中必选包含该字段:返回对应商品仓库ID,不然会报死循环
mapResult.put("wareId", orderInfo.getWareId());
//从orderInfo获取订单明细 封装相关明细信息
List orderDetailList = orderInfo.getOrderDetailList();
if (!CollectionUtils.isEmpty(orderDetailList)) {
List> orderDetailMapList = orderDetailList.stream().map(orderDetail -> {
HashMap detailMap = new HashMap<>();
detailMap.put("skuId", orderDetail.getSkuId());
detailMap.put("skuNum", orderDetail.getSkuNum());
detailMap.put("skuName", orderDetail.getSkuName());
return detailMap;
}).collect(Collectors.toList());
mapResult.put("details", orderDetailMapList);
}
return mapResult;
}
```
## 1.3 消费减库存结果
给仓库系统发送减库存消息后,还要接受减库存成功或者失败的消息。
同样根据《库存管理系统接口手册》中【商品减库结果消息】的说明完成。消费该消息的消息队列监听程序。
接受到消息后主要做的工作就是更新订单状态。
在订单项目中OrderReceiver
```java
/**
* 监听到库存系统扣减库存结果,修改订单状态
*
* @param stockJsonStr {"orderId":1,"satuts":"DEDUCTED"}
* @param message
* @param channel
*/
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange(MqConst.EXCHANGE_DIRECT_WARE_ORDER),
value = @Queue(MqConst.QUEUE_WARE_ORDER),
key = MqConst.ROUTING_WARE_ORDER
))
public void processDeductStockResult(String stockJsonStr, Message message, Channel channel) {
try {
if (StringUtils.isNotBlank(stockJsonStr)) {
log.info("【订单微服务】监听到库存系统扣减库存结果:{}", stockJsonStr);
//1.将扣减库存结果转为Map 得到订单ID以及扣款库存结果
Map map = JSON.parseObject(stockJsonStr, Map.class);
String orderId = (String) map.get("orderId");
String status = (String) map.get("status");
//2.根据结果更新订单状态
if("DEDUCTED".equals(status)){
//扣减成功
orderInfoService.updateOrderStatus(Long.parseLong(orderId), ProcessStatus.WAITING_DELEVER);
}else{
//扣减失败-方案:提供定时任务扫描订单中处理状态为:库存异常。将这些订单作为预警订单提醒工作人员处理
orderInfoService.updateOrderStatus(Long.parseLong(orderId), ProcessStatus.STOCK_EXCEPTION);
}
}
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
log.error("【订单微服务】处理扣减库存结果异常:{}", e);
}
}
```
## 1.4 拆单接口
> YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/747
### 1.3.1 库存系统配置拆单回调接口
**application-dev.yml**
```yaml
order:
split:
url: http://localhost:8204/api/order/orderSplit
```
### 1.3.2 拆单接口控制器
```java
/**
* 库存系统发起拆单请求
* 参数采用表单方式提交
* @param orderId "1"
* @param wareSkuMapStr "[{"wareId":"1","skuIds":["26"]}]"
* @return
*/
@PostMapping("/orderSplit")
public String orderSplit(@RequestParam("orderId") Long orderId, @RequestParam("wareSkuMap") String wareSkuMapStr) {
return orderInfoService.orderSplit(orderId, wareSkuMapStr);
}
```
### 1.3.3 订单实现拆单接口
```java
/**
* 完成拆分订单业务
* @param orderId
* @param wareSkuMap
* @return
*/
String orderSplit(Long orderId, String wareSkuMapStr);
```
### 1.3.4 拆单接口实现类
```java
/**
* 拆分订单处理
* 1.根据提交订单ID查询原始的订单以及订单明细
* 2.遍历仓库跟商品SKU对应关系List 构建新的子订单 订单明细 进行保存
* 3.更新原始订单状态:SPLIT
* 4.按照接口文档构建响应结果[{orderId:1,wareId:"出货仓库ID",orderBody:"",details:[{},{}]},{}]
*
* @param map
* @return
*/
@Override
public String orderSplit(Long orderId, String wareSkuMapStr) {
List allSubOrderInfoList = new ArrayList<>();
//1.根据订单ID查询原始订单以及订单明细
OrderInfo orderInfoOrigin = this.getOrderInfo(orderId);
//原始订单中所有订单商品明细
List orderDetailOriginList = orderInfoOrigin.getOrderDetailList();
//2.根据提交仓库跟SKU对应关系,构建新子订单以及订单明细并保存
//将得到的字符串转为List集合
List