持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21天,点击查看活动详情
每日英语:
Never confuse a single defeat with a final defeat.
翻译:不要把一次失败和最终失败混为一谈。 ——菲茨杰拉德
微信退款
当订单支付了,并且未发货状态下,是可以取消订单,并且此时取消订单需要执行退款操作,但是退款操作并不是那么容易去做的。
退款流程分析
退款流程比支付流程还要复杂,如上图:
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-service的bootstrap.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-service的com.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
}
总结
本篇主要介绍了一下退款流程分析,以及取消订单后的操作(退款记录操作、退款申请操作、退款申请执行),大家需要对退款流程认真理解一下。