记录一下这个Demo,结合RabbitMQ实现微信支付(二维码版),使用内网穿透的条件下完成消息异步回调。要实现微信支付的话必须要有企业认证的微信公众号->个人是无法使用的。
微信开发目前官网有很完善的步骤,可访问官网:https://pay.weixin.qq.com/wiki/doc/api/index.html
要实现的是结合自己的项目,利用消息队列的形式解耦。
以下是官网开发的步骤:
其实步骤已经说的很详细了。
(1)商户后台系统根据用户选购的商品生成订单。
(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;
(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
(4)商户后台系统根据返回的code_url生成二维码。
(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
(8)微信支付系统根据用户授权完成支付交易。
(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
(11)未收到支付通知的情况,商户后台系统调用【查询订单API】(查单实现可参考:支付回调和查单实现指引)。
(12)商户确认订单已支付后给用户发货。
使用消息队列的最大好处就是能够解耦不同服务之间的耦合->当我方服务器生成订单时,不需要等待用户完成支付就可以实现对订单状态的监控。
由于涉及到第三方服务器(微信支付服务器),使用异步消息完全不用担心第三方服务发送的各种事故造成我方服务器的瘫痪。
从用户的角度来说:
在用户成功支付时,此时微信服务器会发送支付结果通知到我方服务器,此时我们只需要把结果发送到消息队列中,让对应服务监听消费即可。
记录一下实现该功能的伪代码
首先引入微信支付的相关SDK、发起HTTP请求的HTTPClient
<!--微信支付,主要用于处理XML和MAP的转换,以及生成随机字符串-->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<!--httpclient支持-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
分为三个角色:订单微服务,支付微服务、微信支付服务
**
在支付微服务中,调用微信统一支付API,从而得到回传的二维码。
创建一个service,实现调用。使用HTTPClient发送HTTP请求
根据微信文档的要求,请求需要发送的都是XML格式,且都是POST请求
/**
* 生成二维码,在service中完成,控制层去调用该方法即可
* 使用微信SDK生成随机字符串,封装参数后发送HTTP请求,拿到回调的信息
* 腾讯的回调信息都是XML格式,因此需要转为MAP结构
* @param paramterMap :传入参数包含:订单号和金额(单位为分,人民币)
* @return
*/
@Override
public Map createNative(Map<String, String> paramterMap) {
try {
//1、封装参数
Map<String,String> param = new HashMap();
param.put("appid", "改成自己的appid"); //应用ID
param.put("mch_id", "改成自己的mch_id"); //商户ID号
param.put("nonce_str", WXPayUtil.generateNonceStr()); //使用微信SDK生成随机字符串
param.put("body", "此处填写相关订单信息"); //订单描述
param.put("out_trade_no",paramterMap.get("outTradeNo")); //商户订单号
param.put("total_fee", paramterMap.get("totalFee")); //交易金额
param.put("spbill_create_ip", "127.0.0.1"); //终端IP
param.put("notify_url", "接收回调信息的地址"); //回调地址
param.put("trade_type", "NATIVE"); //交易类型NATIVE表示二维码支付
//将map转为XML
String paramXml = WXPayUtil.generateSignedXml(param, "此处填写公众号的私钥");
//发送HTTPS请求
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//调用自定义的方法wxPayResultMap(),得到请求结果。
Map<String, String> stringMap = wxPayResultMap(paramXml, url);
return stringMap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/ **
* 封装成一个通用的获取参数
* @param xml 需要添加的xml地址
* @param url 需要请求的url
* @return 发起HTTP请求返回的结果集
*/
private Map<String,String> wxPayResultMap(String xml,String url){
try {
//创建httpClient对象
HttpClient httpClient = new HttpClient(url);
//设置请求信息
httpClient.setHttps(true);
httpClient.setXmlParam(xml);
//发起请求
httpClient.post();
//获取参数
String content = httpClient.getContent();
//转为Map
Map<String, String> map = WXPayUtil.xmlToMap(content);
return map;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
此时使用在控制层调用方法即可,需要传入的参数为->订单号和金额(单位为分,人民币)
严格的说是在订单微服务中,调用feign来实现。
通过POSTMan得到的结果如下:
其中code_url是用来生成二维码的地址
可以使用前端的js插件来完成 : ** qrious.js**
源码:
链接:https://pan.baidu.com/s/1YY6noRkjYpdtcCsJ7WNP8w
提取码:6sbu
使用步骤很简单,建立一个HTML页面,引入JS文件,编写demo代码即可
记得更改value的值-> code_url
<html>
<head>
<title>二维码支付生成title>
<script src="qrious.js"> script>
head>
<body>
<img id="myqrious" >
body>
<script>
var qrious = new QRious({
element:document.getElementById("myqrious"),// 指定的是图片所在的DOM对象
size:250,//指定图片的像素大小
level:'H',//指定二维码的容错级别(H:可以恢复30%的数据)
value:'weixin://wxpay/bizpayurl?pr=MjkreGMzz'//指定二维码图片代表的真正的值
})
script>
html>
那么如何查询订单是否支付了呢?
微信已经提供好了相关的API,根据文档提示操作即可
![image.png](https://img-blog.csdnimg.cn/img_convert/8b1cd26e7636b30b4300f6e0e947b78e.png#align=left&display=inline&height=300&margin=[object Object]&name=image.png&originHeight=600&originWidth=1247&size=77690&status=done&style=none&width=623.5)
代码实现
/**
* 查询订单支付状态
* WXPayUtil ->微信提供的SDK工具类
* @param outTradeNo //商户订单号
* @return
*/
@Override
public Map<String, String> queryStatus(String outTradeNo) {
try {
Map<String,String> param = new HashMap();
param.put("appid", "改成自己的appid"); //应用ID
param.put("mch_id", "改成自己的mch_id"); //商户ID号
param.put("nonce_str", WXPayUtil.generateNonceStr()); //随机数
param.put("out_trade_no",outTradeNo); //商户订单号
//将map转为XML
String paramXml = WXPayUtil.generateSignedXml(param, "改成自己的秘钥");
String url = "https://api.mch.weixin.qq.com/pay/orderquery";
Map<String, String> map = wxPayResultMap(paramXml, url);
return map;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/ **
* 封装成一个通用的获取参数
* @param xml 需要添加的xml地址
* @param url 需要请求的url
* @return 发起HTTP请求返回的结果集
*/
private Map<String,String> wxPayResultMap(String xml,String url){
try {
//创建httpClient对象
HttpClient httpClient = new HttpClient(url);
//设置请求信息
httpClient.setHttps(true);
httpClient.setXmlParam(xml);
//发起请求
httpClient.post();
//获取参数
String content = httpClient.getContent();
//转为Map
Map<String, String> map = WXPayUtil.xmlToMap(content);
return map;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
以上是主动的去查询订单信息,微信会自动将消息传给我吗?
答案是肯定的。在 调用统一下单API时需要传入一个**notify_url **, 表示的就是需要接收微信信息的回调地址
param.put("notify_url", "接收回调信息的地址"); //回调地址
只需要根据支付结果通知API文档的参数来处理即可
此时需要将获取到的信息,丢到消息队列中,让订单微服务去监听消费->如根据订单的结果做订单的删除、修改,商品库存的回退等等操作
此操作在Controller中完成
//注入 RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
/***
* 支付回调
* @param request
* @return
*/
@RequestMapping(value = "/notify/url")
public String notifyUrl(HttpServletRequest request) throws Exception {
//输入流
ServletInputStream inputStream = request.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
//将输入流转为输出流,并写为String字符串,转为Map
byte[] bytes = new byte[1024];
int len = 0;
while ((len=inputStream.read(bytes))!=-1){
byteArrayOutputStream.write(bytes,0,len);
}
byte[] byteArray = byteArrayOutputStream.toByteArray();
//装为字符串
String xml = new String(byteArray);
//将消息转为Map
Map<String, String> map = WXPayUtil.xmlToMap(xml);
System.out.println("map:"+map);
//将map的信息发送到mq
rabbitTemplate.convertAndSend("exchange.order","queue.order", JSON.toJSONString(map));
//微信规定需要返回该xml给它
String result = " ";
return result;
}
随后订单微服务监听并消费即可。
但是问题来了,在开发环境时的回调地址,是本地的地址
如我的地址是:
localhost:18092/wx/pay/notifly
此时微信服务器是无法访问到这个地址的
因此可以使用内网穿透的形式:
百度百科:
内网穿透,也即 NAT 穿透,进行 NAT 穿透是为了使具有某一个特定源 IP 地址和源端口号的数据包不被 NAT 设备屏蔽而正确路由到内网主机。下面就相互通信的主机在网络中与 NAT 设备的相对位置介绍内网穿透方法
使用内网穿透需要一个第三方的供应商来给我们提供一个域名,这个域名一般是第三方的域名,因此我们使用的时候就不需要进行备案即可。
使用内网穿透,能够让公网的用户访问到你的电脑。
目前提供的服务商有很多如:
断续
花生壳
…
此处演示->断续 的过程
访问地址:https://cloud.zhexi.tech/auth/signin
扫码登录,进行一系列的认证即可,需要按提示安装客户端,都弄完了就点击建立隧道:
然后按照这个顺序点击新建
此时会生成一个地址,通过这个地址就能访问到我本地127.0.0.1:8080端口
到了这一步之后其实已经完成了内网穿透,此时通过这个域名就能让微信服务器访问到你的本机。