退款流程分析&取消订单操作

304 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21天,点击查看活动详情

每日英语:

Never confuse a single defeat with a final defeat.

翻译:不要把一次失败和最终失败混为一谈。 ——菲茨杰拉德

微信退款

当订单支付了,并且未发货状态下,是可以取消订单,并且此时取消订单需要执行退款操作,但是退款操作并不是那么容易去做的。

退款流程分析

1606529071900.png

退款流程比支付流程还要复杂,如上图:

1:用户发起取消订单操作,此时要执行退款申请。
2:订单服务接到取消订单后,检查是否符合取消订单情况,如果符合就执行退款申请,退款申请包括修改订单状态为申请退款,同时将退款申请操作发到MQ,让支付服务执行退款申请,但这里是异步执行。
3:此时提示用户申请成功,等待商家审核。
4:支付服务从MQ读取退款信息。
5:读取到退款信息后,向微信服务器发起退款申请操作。
6:微信服务器异步把退款申请结果发送到支付服务,此时并未退款,只是能否退款的结果。
7:支付服务把退款申请结果推送到MQ。
8:订单服务读取退款申请结果。
9:订单服务根据退款申请结果更新订单状态为申请退款成功,等待退款。
10:微信服务器会将退款结果推送到支付服务器。
11:支付服务器将退款结果推送到MQ。
12:订单服务读取推送的退款结果。
13:订单服务器根据退款结果更新订单状态为已退款。

取消订单操作

取消订单操作需要先更改订单状态,同时向MQ发送消息,这里需要保障事务同步,我们可以采用RocketMQ的事务消息实现。

退款记录操作

退款需要做退款记录,订单退款记录表如下:

CREATE TABLE `order_refund` (
  `id` varchar(60) NOT NULL,
  `order_no` varchar(60) NOT NULL COMMENT '退款订单',
  `refund_type` int(1) NOT NULL COMMENT '退款类型:0 整个订单退款,1:指定订单明细退款',
  `order_sku_id` varchar(60) DEFAULT NULL COMMENT '退款订单明细,当refund_type=1的时候填写该ID值',
  `status` int(1) NOT NULL COMMENT '状态,0:申请退款,1:退款成功,2:退款失败',
  `username` varchar(50) NOT NULL,
  `create_time` datetime NOT NULL,
  `money` int(11) NOT NULL COMMENT '退款金额',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

1)Api

我们需要在mall-order-api中创建对应的实体类:com.xz.mall.order.model.OrderRefund

@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "order_refund")
public class OrderRefund implements Serializable {
​
    @TableId(type = IdType.ASSIGN_ID)
    private String id;
    private String orderNo;
    private Integer refundType;
    private String orderSkuId;
    private String username;
    private Integer status;
    private Date createTime;
    private Integer money;
}

2)Dao

mall-order-service中添加com.xz.mall.order.mapper.OrderRefundMapper

public interface OrderRefundMapper extends BaseMapper<OrderRefund> {
}

退款申请操作

由于我们要用到MQ,所以需要配置MQ。

1)配置RocketMQ

修改mall-order-servicebootstrap.yml,添加RocketMQ配置:

#RocketMQ配置
rocketmq:
  name-server: 192.168.xxx.xxx:9876
  producer:
    #订单消息生产者
    group: order-provider
    send-message-timeout: 300000
    compress-message-body-threshold: 4096
    max-message-size: 4194304
    retry-times-when-send-async-failed: 0
    retry-next-server: true
    retry-times-when-send-failed: 2

2)Service

修改com.xz.mall.order.service.OrderService添加退款操作,代码如下:

/****
 * 申请退款(取消订单)
 * @return
 */
int refund(OrderRefund orderRefund);

修改com.xz.mall.order.service.impl.OrderServiceImpl添加退款操作,代码如下:

/****
 * 申请退款(取消订单)
 * @return
 */
@Transactional
@Override
public int refund(OrderRefund orderRefund) {
    //退款申请记录
    int icount = orderRefundMapper.insert(orderRefund);
​
    //订单状态变更
    Order order = new Order();
    order.setOrderStatus(4);            //申请退款
    //条件
    QueryWrapper<Order> queryWrapper = new QueryWrapper<Order>();
    queryWrapper.eq("id",orderRefund.getOrderNo());
    queryWrapper.eq("username",orderRefund.getUsername());
    //原来是已支付待发货状态
    queryWrapper.eq("order_status",1);  //待发货
    queryWrapper.eq("pay_status",1);    //已支付
    int mcount = orderMapper.update(order,queryWrapper);
    return mcount;
}

3)事务消息监听

mall-order-service中创建com.xz.mall.order.mq.RefundTransactionListenerImpl用于向RocketMQ发送事务消息,同时更新订单状态,并记录退款订单信息,代码如下:

@Component
@RocketMQTransactionListener(txProducerGroup = "refundtx")
public class RefundTransactionListenerImpl implements RocketMQLocalTransactionListener {
​
    @Autowired
    private OrderService orderService;
​
    /***
     * 发送prepare消息成功后回调该方法用于执行本地事务
     * @param message:回传的消息,利用transactionId即可获取到该消息的唯一Id
     * @param o:调用send方法时传递的参数,当send时候若有额外的参数可以传递到send方法中,这里能获取到
     * @return
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        try {
            //================本地事务操作开始=====================================
            //修改本地状态
            int count = orderService.refund((OrderRefund) o);
​
            //如果申请退款失败,则回滚half消息
            if(count<=0){
                return RocketMQLocalTransactionState.ROLLBACK;
            }
            //================本地事务操作结束=====================================
        } catch (Exception e) {
            //异常,消息回滚
            e.printStackTrace();
            return RocketMQLocalTransactionState.ROLLBACK;
        }
        return RocketMQLocalTransactionState.UNKNOWN;
    }
​
    /***
     * 消息回查
     * @param message
     * @return
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        return RocketMQLocalTransactionState.COMMIT;
    }
}

4)数据验签

退款操作我们也防止数据泄露,也做验签校验,修改com.xz.mall.order.pay.WeixinPayParam添加退款参数封装,并做验签操作,代码如下:

/****
 * 微信退款参数封装
 * 对参数进行签名
 * 对整体参数进行加密
 * @return
 */
public String weixinRefundParam(Order order,String outrefundno) throws Exception {
    //定义Map封装参数
    Map<String,String> dataMap = new HashMap<String,String>();
    dataMap.put("out_trade_no", order.getId());
    dataMap.put("out_refund_no", outrefundno);
    //dataMap.put("total_fee", String.valueOf(order.getMoneys()));
    dataMap.put("total_fee", "1"); //1分钱测试
    //退款金额
    //dataMap.put("refund_fee", String.valueOf(order.getMoneys()));
    dataMap.put("refund_fee", "1");//1分钱测试
    dataMap.put("notify_url", "http://2cw4969042.wicp.vip:25082/wx/refund/result");
    //生成签名,并且参数加密
    return signature.security(dataMap);
}

5)事务消息发送

修改mall-order-servicecom.xz.mall.order.controller.OrderController,添加取消订单退款申请方法,代码如下:

@Autowired
private RocketMQTemplate rocketMQTemplate;
​
/***
 * 取消订单
 */
@PutMapping(value = "/refund/{id}")
public RespResult refund(@PathVariable(value = "id")String id,HttpServletRequest request) throws Exception {
    String userName = "gp";
​
    //查询商品信息
    Order order = orderService.getById(id);
​
    //已支付,待发货,才允许取消订单
    if(order.getOrderStatus().intValue()==1 && order.getPayStatus().intValue()==1){
        //退款记录
        OrderRefund orderRefund = new OrderRefund(
                IdWorker.getIdStr(),
                order.getId(),
                0,//0 整个订单退款,1 单个明细退款
                null,
                userName,
                0,//状态,0:申请退款,1:退款成功,2:退款失败
                new Date(),
                order.getMoneys()   //退款金额
        );
​
        //发送事务消息[退款加密信息]
        Message message = MessageBuilder.withPayload(weixinPayParam.weixinRefundParam(order,orderRefund.getId())).build();
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction("refundtx", "refund", message, orderRefund);
        if(transactionSendResult.getSendStatus()== SendStatus.SEND_OK){
            return RespResult.error("申请退款成功,等待退款!");
        }
        return RespResult.error("不符合取消订单条件,无法退货!");
    }
    return RespResult.error("订单已发货,或无法退货!");
}

退款申请执行

在支付服务中监听消息,并调用微信支付服务的申请退款接口实现退款操作。

1)Service

接口:修改com.xz.mall.pay.service.WeixinPayService添加退款方法,代码如下:

/***
 * 退款
 * @param map
 * @return
 */
Map<String, String>  refund(Map<String, String> map) throws Exception;

实现类:修改com.xz.mall.pay.service.impl.WeixinPayServiceImpl添加退款实现,代码如下:

/***
 * 退款申请
 * @param map : 包含了out_trade_no
 * @return
 */
@Override
public Map<String, String>  refund(Map<String, String> map) throws Exception {
    //退款申请
    Map<String, String> resultMap = wxPay.refund(map);
    return resultMap;
}

2)退款申请监听

mall-pay-service中创建com.xz.mall.pay.mq.RefundResultListener,并实现对退款申请的监听操作,监听到数据后,执行退款接口调用,代码如下:

@Component
@RocketMQMessageListener(topic = "refund", consumerGroup = "orderrefund-group")
public class RefundResultListener implements RocketMQListener,RocketMQPushConsumerLifecycleListener {
​
    @Autowired
    private WeixinPayService weixinPayService;
​
    @Autowired
    private Signature signature;
​
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
​
    /***
     * 监听消息
     * 实现RocketMQPushConsumerLifecycleListener监听器之后,此方法不调用
     * @param message
     */
    @Override
    public void onMessage(Object message) {
    }
​
    /***
     * 消息监听:退款事务监听
     * @param consumer
     */
    @Override
    public void prepareStart(DefaultMQPushConsumer consumer) {
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                try {
                    for (MessageExt msg : msgs) {
                        String result = new String(msg.getBody(),"UTF-8");
​
                        //数据解析,并验签校验
                        Map<String, String> map = signature.security(result);
                        if(map!=null){
                            //执行退款申请
                            Map<String, String>  resultMap = weixinPayService.refund(map);
​
                            System.out.println("退款申请resultMap:"+resultMap);
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                }
                //消费状态
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
    }
}

退款申请resultMap:

{
  transaction_id=4200000724202011271996336311,
  nonce_str=f5cwyiRiGGoBOeTt,
  out_refund_no=1332312259728322562,
  sign=58FD3D8462D3A8893B855A27A1616CECBBF28B88B5482DE9E740B9206834E729,
  return_msg=OK,
  mch_id=1581433991,
  refund_id=50300706712020112804289880911,
  cash_fee=1,
  out_trade_no=1332311961987264513,
  coupon_refund_fee=0,
  refund_channel=,
  appid=wxb208ad76a7c389a9,
  refund_fee=1,
  total_fee=1,
  result_code=SUCCESS,
  coupon_refund_count=0,
  cash_refund_fee=1,
  return_code=SUCCESS
}

总结

本篇主要介绍了一下退款流程分析,以及取消订单后的操作(退款记录操作、退款申请操作、退款申请执行),大家需要对退款流程认真理解一下。