3.支付回调逻辑处理
3.1 需求分析
在完成支付后,修改订单状态为已支付,并记录订单日志。
3.2 实现思路
(1)接受微信支付平台的回调信息(xml)
<xml><appid><![CDATA[wx8397f8696b538317]]></appid>
<bank_type><![CDATA[CFT]]></bank_type>
<cash_fee><![CDATA[1]]></cash_fee>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[N]]></is_subscribe>
<mch_id><![CDATA[1473426802]]></mch_id>
<nonce_str><![CDATA[c6bea293399a40e0a873df51e667f45a]]></nonce_str>
<openid><![CDATA[oNpSGwbtNBQROpN_dL8WUZG3wRkM]]></openid>
<out_trade_no><![CDATA[1553063775279]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[DD4E5DF5AF8D8D8061B0B8BF210127DE]]></sign>
<time_end><![CDATA[20190320143646]]></time_end>
<total_fee>1</total_fee>
<trade_type><![CDATA[NATIVE]]></trade_type>
<transaction_id><![CDATA[4200000248201903206581106357]]></transaction_id>
</xml>
(2)收到通知后,调用查询接口查询订单。
(3)如果支付结果为成功,则调用修改订单状态和记录订单日志的方法。
3.3 代码实现
3.3.1 内网映射工具EchoSite
在请求统一下单接口时,有个参数notify_url ,这个是回调地址,也就是在支付成功后微信支付会自动访问这个地址,通知业务系统支付完成。但这个地址必须是互联网可以访问的(也就是有域名的)。
那么如何测试呢?我们可以借助一个工具 EchoSite 内网映射工具
(1)打开网址: https://www.echosite.cn/ 注册用户,登录到控制台下载客户端。
(2)支付3元买一个域名(可以用1个月),点击域名端口—抢注域名
(3)下载echosite ,添加config.yml (在软件的配置文件中改)
# 这是你的 EchoSite 购买域名的服务器标志
server_addr: cross.echosite.cn:4443
trust_host_root_certs: false
echosite_id: #从官网获得
echosite_token: #从官网获得
# 以下是你需要开启的通道,只能开启属于你的域名通道
# 以下分别是 http 和 https 以及 tcp 协议的示例
tunnels:
name1:
subdomain: "changgou"
proto:
http: 127.0.0.1:9010
然后在echosite目录中输入以下命令
echosite -config=config.yml start-all
运行效果如下:
这样你购买的域名就映射到127.0.0.1:9011上了。 ctrl+c 结束程序
怎么才能验证域名是否映射到你的计算机上了呢?
WxPayController新增notifyLogic方法
/**
* 回调
*/
@RequestMapping("/notify")
public void notifyLogic(){
System.out.println("支付成功回调。。。。");
}
测试:
1)通过本地方式访问该接口
2)通过域名形式访问该接口
3.3.2 接收回调信息
(1)修改支付微服务配置文件
wxpay:
notify_url: http://weizhaohui.cross.echosite.cn/wxpay/notify #回调地址
(2)修改WxPayServiceImpl ,引入
@Value( "${wxpay.notify_url}" )
private String notifyUrl;
(3)修改WxPayServiceImpl 的nativePay方法
map.put("notify_url",notifyUrl);//回调地址
测试: 重新生成订单并支付。可以发现控制台在不断的触发支付回调通知。这是为什么呢?
注意
1、同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
2、后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信会判定本次通知失败,重新发送通知,直到成功为止(在通知一直不成功的情况下,微信总共会发起10次通知,通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m),但微信不保证通知最终一定能成功。
3.3.3回调消息接收并转换
微信支付平台发送给回调地址的数据是二进制流,我们需要提取二进制流转换为字符串,这个字符串就是xml格式。
(1)在changgou_common工程添加工具类ConvertUtils。(资源提供:资源\类文件\工具类)
package com.changgou.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 转换工具类
*/
public class ConvertUtils {
/**
* 输入流转换为xml字符串
* @param inputStream
* @return
*/
public static String convertToString(InputStream inputStream) throws IOException {
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inputStream.close();
String result = new String(outSteam.toByteArray(), "utf-8");
return result;
}
}
(2)修改notifyLogic方法
@RequestMapping("/notify")
public void notifyLogic(HttpServletRequest request, HttpServletResponse response){
System.out.println("支付成功回调");
try{
//输入流转换为字符串
String xml = ConvertUtils.convertToString(request.getInputStream());
System.out.println(xml);
//给微信一个结果通知
response.setContentType("text/xml");
String data="<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
response.getWriter().write(data);
}catch (Exception e){
e.printStackTrace();
}
}
测试后,在控制台看到输出的消息
<xml><appid><![CDATA[wx8397f8696b538317]]></appid>
<bank_type><![CDATA[CFT]]></bank_type>
<cash_fee><![CDATA[1]]></cash_fee>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[N]]></is_subscribe>
<mch_id><![CDATA[1473426802]]></mch_id>
<nonce_str><![CDATA[c6bea293399a40e0a873df51e667f45a]]></nonce_str>
<openid><![CDATA[oNpSGwbtNBQROpN_dL8WUZG3wRkM]]></openid>
<out_trade_no><![CDATA[1553063775279]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[DD4E5DF5AF8D8D8061B0B8BF210127DE]]></sign>
<time_end><![CDATA[20190320143646]]></time_end>
<total_fee>1</total_fee>
<trade_type><![CDATA[NATIVE]]></trade_type>
<transaction_id><![CDATA[4200000248201903206581106357]]></transaction_id>
</xml>
我们可以将此xml字符串,转换为map,提取其中的out_trade_no(订单号),根据订单号修改订单状态。
3.3.4 查询订单验证通知
(1)WxPayService新增方法定义
/**
* 查询订单
* @param orderId
* @return
*/
Map queryOrder(String orderId);
(2)WxPayServiceImpl实现方法
@Override
public Map queryOrder(String orderId) {
Map map=new HashMap( );
map.put( "out_trade_no", orderId );
try {
return wxPay.orderQuery( map );
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
(3)修改notifyLogic方法
@RequestMapping("/notify")
public void notifyLogic(HttpServletRequest request, HttpServletResponse response){
System.out.println("支付成功回调");
try{
//输入流转换为字符串
String xml = ConvertUtils.convertToString(request.getInputStream());
System.out.println(xml);
//基于微信发送的通知内容,完成后续的业务逻辑处理
Map<String, String> map = WXPayUtil.xmlToMap(xml);
if ("SUCCESS".equals(map.get("result_code"))){
//查询订单
Map result = wxPayService.queryOrder(map.get("out_trade_no"));
System.out.println("查询订单结果:"+result);
if ("SUCCESS".equals(result.get("result_code"))){
//将订单的消息发送到mq'
Map message = new HashMap();
message.put("orderId",result.get("out_trade_no"));
message.put("transactionId",result.get("transaction_id"));
//消息的发送
rabbitTemplate.convertAndSend("", RabbitMQConfig.ORDER_PAY, JSON.toJSONString(message));
//完成双向通信
rabbitTemplate.convertAndSend("paynotify","",result.get("out_trade_no"));
}else {
//输出错误原因
System.out.println(map.get("err_code_des"));
}
}else{
//输出错误原因
System.out.println(map.get("err_code_des"));
}
//给微信一个结果通知
response.setContentType("text/xml");
String data="<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
response.getWriter().write(data);
}catch (Exception e){
e.printStackTrace();
}
}
(4)rabbitmq中添加order_pay队列
3.3.5 修改订单状态
(1)com.changgou.order.listener包下创建OrderPayListener
package com.changgou.order.listener;
import com.alibaba.fastjson.JSON;
import com.changgou.order.config.RabbitMQConfig;
import com.changgou.order.service.OrderService;
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 OrderPayListener {
@Autowired
private OrderService orderService;
@RabbitListener(queues = RabbitMQConfig.ORDER_PAY)
public void receivePayMessage(String message){
System.out.println("接收到了订单支付的消息:"+message);
Map map = JSON.parseObject(message, Map.class);
//调用业务层,完成订单数据库的修改
orderService.updatePayStatus((String)map.get("orderId"),(String)map.get("transactionId"));
}
}
(2)OrderService接口新增方法定义
void updatePayStatus(String orderId, String transactionId);
(3)OrderServiceImpl新增方法实现
@Override
@Transactional
public void updatePayStatus(String orderId, String transactionId) {
//1.查询订单
Order order = orderMapper.selectByPrimaryKey(orderId);
if (order != null && "0".equals(order.getPayStatus())){
//2.修改订单的支付状态
order.setPayStatus("1");
order.setOrderStatus("1");
order.setUpdateTime(new Date());
order.setPayTime(new Date());
order.setTransactionId(transactionId); //微信返回的交易流水号
orderMapper.updateByPrimaryKeySelective(order);
//3.记录订单日志
OrderLog orderLog = new OrderLog();
orderLog.setId(idWorker.nextId()+"");
orderLog.setOperater("system");
orderLog.setOperateTime(new Date());
orderLog.setOrderStatus("1");
orderLog.setPayStatus("1");
orderLog.setRemarks("交易流水号:"+transactionId);
orderLog.setOrderId(orderId);
orderLogMapper.insert(orderLog);
}
}