使用消息队列完成微信支付(含内网穿透)

19 篇文章 0 订阅

序言

记录一下这个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)商户确认订单已支付后给用户发货。

使用消息队列解耦

在这里插入图片描述

使用消息队列的最大好处就是能够解耦不同服务之间的耦合->当我方服务器生成订单时,不需要等待用户完成支付就可以实现对订单状态的监控。

由于涉及到第三方服务器(微信支付服务器),使用异步消息完全不用担心第三方服务发送的各种事故造成我方服务器的瘫痪。

从用户的角度来说:

  1. 用户在我方服务器点击下单,此时支付微服务就会生成相关的参数(如订单号,企业公众号相关参数),通过HTTP请求,调用统一下单API实现下单。
  2. 微信支付服务器收到请求后就会在内部创建订单,将订单参数以及支付地址发送到我方服务器
  3. 我方服务器此时就可以拿到这个地址,通过第三方插件来生成二维码->用户此时就可以扫码支付
  4. 用户进入扫码支付阶段,完全是跟微信服务器进行对接的,此时跟我方服务无关。

image.png

在用户成功支付时,此时微信服务器会发送支付结果通知到我方服务器,此时我们只需要把结果发送到消息队列中,让对应服务监听消费即可。

image.png

实现微信支付

记录一下实现该功能的伪代码
首先引入微信支付的相关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得到的结果如下:
image.png
其中code_url是用来生成二维码的地址

生成访问二维码

可以使用前端的js插件来完成 : ** qrious.js**
源码:
链接:https://pan.baidu.com/s/1YY6noRkjYpdtcCsJ7WNP8w
提取码:6sbu

使用步骤很简单,建立一个HTML页面,引入JS文件,编写demo代码即可

记得更改value的值-> code_url

<html>
<head>
<title>二维码支付生成</title>
<!--1.引入js  
2. 创建一个img标签 用来存储显示二维码的图片 
3.创建js对象 4.设置js对象的配置项-->

<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;
        }
        
    }

依旧在控制层调用,传入订单编号即可
image.png

异步消息通知

以上是主动的去查询订单信息,微信会自动将消息传给我吗?

答案是肯定的。在 调用统一下单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 = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";

        return result;


    }

随后订单微服务监听并消费即可。

但是问题来了,在开发环境时的回调地址,是本地的地址

如我的地址是:

localhost:18092/wx/pay/notifly

此时微信服务器是无法访问到这个地址的

因此可以使用内网穿透的形式:

在这里插入图片描述

百度百科:

内网穿透,也即 NAT 穿透,进行 NAT 穿透是为了使具有某一个特定源 IP 地址和源端口号的数据包不被 NAT 设备屏蔽而正确路由到内网主机。下面就相互通信的主机在网络中与 NAT 设备的相对位置介绍内网穿透方法

使用内网穿透需要一个第三方的供应商来给我们提供一个域名,这个域名一般是第三方的域名,因此我们使用的时候就不需要进行备案即可。

使用内网穿透,能够让公网的用户访问到你的电脑

目前提供的服务商有很多如:

断续

花生壳

此处演示->断续 的过程

访问地址:https://cloud.zhexi.tech/auth/signin

扫码登录,进行一系列的认证即可,需要按提示安装客户端,都弄完了就点击建立隧道:

在这里插入图片描述

然后按照这个顺序点击新建

在这里插入图片描述

此时会生成一个地址,通过这个地址就能访问到我本地127.0.0.1:8080端口

在这里插入图片描述

到了这一步之后其实已经完成了内网穿透,此时通过这个域名就能让微信服务器访问到你的本机。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值