微信扫码支付完整实现示例

阅读文档

在线微信支付开发文档:

https://pay.weixin.qq.com/wiki/doc/api/index.html

支付接口调用过程

按API要求组装参数,以XML方式发送(POST)给微信支付接口(URL),微信支付接口也是以XML方式给予响应。程序根据返回的结果(其中包括支付URL)生成二维码或判断订单状态。

核心参数

appid:微信公众账号或开放平台APP的唯一标识

mch_id:商户号  (配置文件中的partner)

partnerkey:商户密钥

sign:数字签名, 根据微信官方提供的密钥和一套算法生成的一个加密信息, 就是为了保证交易的安全性

准备SDK

SDK核心常用方法:

获取随机字符串

WXPayUtil.generateNonceStr()

MAP转换为XML字符串(自动添加签名)

 WXPayUtil.generateSignedXml(param, partnerkey)

XML字符串转换为MAP

WXPayUtil.xmlToMap(result)

下载SDK

在这里插入图片描述

安装SDK到本地仓库

在这里插入图片描述

微信扫码支付快速开始

引入SDK

     <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>3.0.9</version>
        </dependency>

创建WxConfig类

创建PayConfig 类 ,继承抽象类WXPayConfig。

public abstract class WXPayConfig {

    /**
     * 获取 App ID
     *
     * @return App ID
     */
    abstract String getAppID();


    /**
     * 获取 Mch ID
     *
     * @return Mch ID
     */
    abstract String getMchID();


    /**
     * 获取 API 密钥
     *
     * @return API密钥
     */
    abstract String getKey();


    /**
     * 获取商户证书内容
     *
     * @return 商户证书内容
     */
    abstract InputStream getCertStream();
	
	//........
}

需要注意:

WXPayConfig是抽象类,且WXPayConfig的抽象方法默认使用的修饰符是default,default修饰符在其他包内是没有访问权限的,所以继承WXPayconfig抽象类实现抽象方法时需注意包结构。

解决方案

1.修改官方wxpay-sdk源码,将抽象方法的修饰符从默认修改为public,然后重新打包到本地或者私服。

2.使用时注意包结构与官方wxpay-sdk源码包结构一致即可。
public class PayConfig extends WXPayConfig {
    /**
     * 获取 App ID
     *
     * @return
     */
    @Override
    String getAppID() {
        return "appId";
    }

    /**
     * 商户号
     *
     * @return
     */
    @Override
    String getMchID() {
        return "machId";
    }

    /**
     * 获取 API 密钥
     *
     * @return
     */
    @Override
    String getKey() {
        return "key";
    }

    /**
     * 获取商户证书内容
     *
     * @return
     */
    @Override
    InputStream getCertStream() {
        return null;
    }

    /**
     * 获取WXPayDomain, 用于多域名容灾自动切换
     *
     * @return
     */
    @Override
    IWXPayDomain getWXPayDomain() {
        return new IWXPayDomain() {
            @Override
            public void report(String s, long l, Exception e) {

            }

            @Override
            public DomainInfo getDomain(WXPayConfig wxPayConfig) {
            	//微信API接口地址的前半部分,如统一下单API接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder
                return new DomainInfo("api.mch.weixin.qq.com", true);
            }
        };
    }
}

统一下单

 	@Test
    void createNative() throws Exception {
        PayConfig config = new PayConfig();

        //封装请求参数
        Map<String,String> map=new HashMap();
        map.put("appid",config.getAppID());//公众账号ID
        map.put("mch_id",config.getMchID());//商户号
        map.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串
        map.put("body","腾讯充值中心-QQ会员充值");//商品描述
        map.put("out_trade_no","2016090910595900001234");//订单号
        map.put("total_fee","100");//金额,单位分
        map.put("spbill_create_ip","127.0.0.1");//终端IP
        map.put("notify_url","http://www.baidu.com");//回调地址
        map.put("trade_type","NATIVE");//此处指定为扫码支付

		//添加签名
        String toXmlParams = WXPayUtil.generateSignedXml(map, config.getKey());

        //xml格式的参数
        System.out.println("XML参数:"+toXmlParams);

        //发送请求
        WXPayRequest wxPayRequest=new WXPayRequest(config);
        //统一下单API接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder后半截
        String xmlResult = wxPayRequest.requestWithCert("/pay/unifiedorder", null, toXmlParams, false);
        System.out.println("请求结果:"+xmlResult);

        //解析返回结果
        Map<String, String> mapResult = WXPayUtil.xmlToMap(xmlResult);
        String code_url = mapResult.get("code_url");
        System.out.println("解析取值:"+code_url);
    }

XML参数:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xml>
<nonce_str>GlTPbFnwzznWamng5iv9IrzWvAnnD3cx</nonce_str>
<out_trade_no>2016090910595900001234</out_trade_no>
<appid>appid</appid>
<total_fee>100</total_fee>
<sign>4066AE6B9BD7D4814041FD7B74D75AC5</sign>
<trade_type>NATIVE</trade_type>
<mch_id>mch_id</mch_id>
<body>腾讯充值中心-QQ会员充值</body>
<notify_url>http://www.baidu.com</notify_url>
<spbill_create_ip>127.0.0.1</spbill_create_ip>
</xml>

请求结果:

<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[appid]]></appid>
<mch_id><![CDATA[mch_id]]></mch_id>
<nonce_str><![CDATA[MP4VAsQgBnAPT031]]></nonce_str>
<sign><![CDATA[560FD6149A30645A5156ABC3248DB1E7]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx18225930904975e36a86b5d28729c90000]]></prepay_id>
<trade_type><![CDATA[NATIVE]]></trade_type>
<code_url><![CDATA[weixin://wxpay/bizpayurl?pr=hGzJAG7zz]]></code_url>
</xml>

解析取值:

解析取值: weixin://wxpay/bizpayurl?pr=hGzJAG6zz

生成二维码

使用二维码JS插件qrcodejs.js生成二维码。

下载qrcodejs.js: http://davidshimjs.github.io/qrcodejs/

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ko" lang="ko">
<head>
<title>Cross-Browser QRCode generator for Javascript</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="qrcode.js"></script>
</head>
<body>
<input id="text" type="text" value="http://jindo.dev.naver.com/collie" style="width:80%" /><br />
<div id="qrcode" style="width:100px; height:100px; margin-top:15px;"></div>

<script type="text/javascript">
var qrcode = new QRCode(document.getElementById("qrcode"), {
	width : 100,
	height : 100
});

function makeCode () {		
	var elText = document.getElementById("text");
	
	if (!elText.value) {
		alert("Input a text");
		elText.focus();
		return;
	}
	
	qrcode.makeCode(elText.value);
}

makeCode();

$("#text").
	on("blur", function () {
		makeCode();
	}).
	on("keydown", function (e) {
		if (e.keyCode == 13) {
			makeCode();
		}
	});
</script>
</body>

只需要将code_url(支付URl) 解析取值: weixin://wxpay/bizpayurl?pr=hGzJAG6zz作为qrcode.makeCode()方法参数即可生成对应支付二维码。
在这里插入图片描述

查询订单

接口地址:https://api.mch.weixin.qq.com/pay/orderquery

@Test
    void queryPayStatus(){
        PayConfig payConfig = new PayConfig();

        try {
            //封装参数
            Map<String,String> param=new HashMap<>();
            param.put("appid", payConfig.getAppID());
            param.put("mch_id", payConfig.getMchID());
            param.put("out_trade_no","2016090910595900001234");
            param.put("nonce_str",WXPayUtil.generateNonceStr());

			 //添加签名
            String xmlParam = WXPayUtil.generateSignedXml(param, payConfig.getKey());
            //xml格式的参数
            System.out.println("XML参数:"+xmlParam);

            //调用接口
            WXPayRequest wxPayRequest=new WXPayRequest(payConfig);
            String result = wxPayRequest.requestWithCert("/pay/orderquery", null, xmlParam, false);
            System.out.println("请求结果:"+result);

            //解析结果
            Map<String, String> map = WXPayUtil.xmlToMap(result);
            System.out.println("订单状态:"+map.get("trade_state"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

XML参数:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xml>
<nonce_str>qCpy0lggeyqMchnAXMZ5snLh1fZY5KNE</nonce_str>
<out_trade_no>2016090910595900001234</out_trade_no>
<appid>appid</appid>
<sign>D0107F26BB64DCEC9921EBEAE2C4A43C</sign>
<mch_id>mch_id</mch_id>
</xml>

请求结果:

<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[appid]]></appid>
<mch_id><![CDATA[mch_id]]></mch_id>
<device_info><![CDATA[]]></device_info>
<nonce_str><![CDATA[N83f7sZVK8XbN6sA]]></nonce_str>
<sign><![CDATA[3EDD8C92F2998CD5E55976B5646EEAE2]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<total_fee>100</total_fee>
<out_trade_no><![CDATA[2016090910595900001234]]></out_trade_no>
<trade_state><![CDATA[NOTPAY]]></trade_state>
<trade_state_desc><![CDATA[订单未支付]]></trade_state_desc>
</xml>

订单状态:

订单状态:NOTPAY

关闭订单

关闭订单接口: https://api.mch.weixin.qq.com/pay/closeorder

    @Test
    void colseOrder(){
        PayConfig payConfig = new PayConfig();

        try {
            //封装参数
            Map<String,String> param=new HashMap<>();
            param.put("appid", payConfig.getAppID());
            param.put("mch_id", payConfig.getMchID());
            param.put("out_trade_no","2016090910595900001234");
            param.put("nonce_str",WXPayUtil.generateNonceStr());

            //添加签名
            String xmlParam = WXPayUtil.generateSignedXml(param, payConfig.getKey());
            //xml格式的参数
            System.out.println("XML参数:"+xmlParam);

            //调用接口
            WXPayRequest wxPayRequest=new WXPayRequest(payConfig);
            String result = wxPayRequest.requestWithCert("/pay/closeorder", null, xmlParam, false);
            System.out.println("请求结果:"+result);

            //解析结果
            Map<String, String> map = WXPayUtil.xmlToMap(result);
            System.out.println("返回信息:"+map.get("return_msg"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

XML参数:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xml>
<nonce_str>qZFauYqvQzDNlh5yt0JxFs4BRY4bJYsP</nonce_str>
<out_trade_no>2016090910595900001234</out_trade_no>
<appid>appid</appid>
<sign>26876F9A5EEDC7FE2C4967E60C950BD1</sign>
<mch_id>mch_id</mch_id>
</xml>

请求结果:

<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[appid]]></appid>
<mch_id><![CDATA[mch_id]]></mch_id>
<sub_mch_id><![CDATA[]]></sub_mch_id>
<nonce_str><![CDATA[fSqP1C9PmeLNTjIX]]></nonce_str>
<sign><![CDATA[7A9B29A61F10428E7A044B660F1D0F07]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
</xml>

返回信息:

返回信息:OK

支付成功回调

异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。

使用内网映射

在请求统一下单接口时,参数notify_url 是回调地址,也就是在支付成功后微信支付会自动访问这个地址,通知业务系统支付完成。但这个地址必须是互联网可以访问的,也就是需要有域名。

这里使用NATAPP实现内网映射,文档齐全,很简单。

在这里插入图片描述

    @RequestMapping("/payNotify")
    public void payNotify(String xml) {

        PayConfig payConfig = new PayConfig();

        System.out.println("回调信息:" + xml);

        try {
            //将xml解析成map
            Map<String, String> map = WXPayUtil.xmlToMap(xml);
            //验证签名
            boolean signatureValid = WXPayUtil.isSignatureValid(map, payConfig.getKey());

            System.out.println("验证签名是否正确:" + signatureValid);
            System.out.println(map.get("result_code"));

            //TODO 如果签名正确:1.修改订单状态 2.将订单Id发送消息给mq 否则:日志记录
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

推送支付通知

当用户完成扫码支付后,使用WebSocket双向通信实现推送支付结果通知,决定支付结果的页面跳转。WebSocket是 HTML5 中一种新的通信协议,能够实现浏览器与服务器之间全双工通信。

RabbitMQ Web STOMP插件

使用RabbitMQ的Web STOMP 插件,实现浏览器与服务端的全双工通信。Web STOMP插件是利用WebSocket对STOMP协议进行了一次桥接,从而实现浏览器与服务端的双向通信。

安装插件

进入mq容器

docker exec -it 容器名称 /bin/bash

开启web stomp插件

rabbitmq-plugins enable rabbitmq_web_stomp rabbitmq_web_stomp_examples
root@dafe80f84042:/# rabbitmq-plugins enable rabbitmq_web_stomp rabbitmq_web_stomp_examples
Enabling plugins on node rabbit@dafe80f84042:
rabbitmq_web_stomp
rabbitmq_web_stomp_examples
The following plugins have been configured:
  rabbitmq_management
  rabbitmq_management_agent
  rabbitmq_stomp
  rabbitmq_web_dispatch
  rabbitmq_web_stomp
  rabbitmq_web_stomp_examples
Applying plugin configuration to rabbit@dafe80f84042...
The following plugins have been enabled:
  rabbitmq_stomp
  rabbitmq_web_stomp
  rabbitmq_web_stomp_examples

started 3 plugins.
root@dafe80f84042:/# 

在这里插入图片描述

容器提交为镜像

docker commit 容器ID mq-stomp

停止原容器

docker stop 容器ID

创建新容器

docker run -id --name mqstomp -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin123 -p 15672:15672 -p 5672:5672 -p 15674:15674 -p 15670:15670  mq-stomp

消息推送测试

插件安装后,浏览器访问http://IP:15670即可查看stomp的例子代码 

在这里插入图片描述
Simple Echo Server
在这里插入图片描述
右键查看网页源码
在这里插入图片描述
准备HTML页面

<!DOCTYPE html>
<html>
<head>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="../static/stomp.min.js"></script>
    <style>
        .box {
            width: 440px;
            float: left;
            margin: 0 20px 0 20px;
        }

        .box div, .box input {
            border: 1px solid;
            -moz-border-radius: 4px;
            border-radius: 4px;
            width: 100%;
            padding: 5px;
            margin: 3px 0 10px 0;
        }

        .box div {
            border-color: grey;
            height: 300px;
            overflow: auto;
        }

        div code {
            display: block;
        }

        #first div code {
            -moz-border-radius: 2px;
            border-radius: 2px;
            border: 1px solid #eee;
            margin-bottom: 5px;
        }

        #second div {
            font-size: 0.8em;
        }
    </style>
    <title>RabbitMQ Web STOMP Examples : Echo Server</title>
    <link href="main.css" rel="stylesheet" type="text/css"/>
</head>
<body lang="en">
<h1><a href="index.html">RabbitMQ Web STOMP Examples</a> > Echo Server</h1>

<div id="first" class="box">
    <h2>Received</h2>
    <div></div>
    <form><input autocomplete="off" value="Type here..."></input></form>
</div>

<div id="second" class="box">
    <h2>Logs</h2>
    <div></div>
</div>

<script>
    var has_had_focus = false;
    var pipe = function (el_name, send) {
        var div = $(el_name + ' div');
        var inp = $(el_name + ' input');
        var form = $(el_name + ' form');

        var print = function (m, p) {
            p = (p === undefined) ? '' : JSON.stringify(p);
            div.append($("<code>").text(m + ' ' + p));
            div.scrollTop(div.scrollTop() + 10000);
        };

        if (send) {
            form.submit(function () {
                send(inp.val());
                inp.val('');
                return false;
            });
        }
        return print;
    };

    //创建STOMP客户端
    var client = Stomp.client('ws://ip:15674/ws');
    client.debug = pipe('#second');

    var print_first = pipe('#first', function (data) {
        client.send('/exchange/stompTest', {"content-type": "text/plain"}, data);
    });
    
    var on_connect = function (x) {
    	 //订阅(Subscribe)消息
        id = client.subscribe("/exchange/stompTest", function (d) {
            print_first(d.body);
        });
    };
    
    var on_error = function () {
        console.log('error');
    };
    //设置连接参数
    client.connect('test', 'test', on_connect, on_error, '/');

    $('#first input').focus(function () {
        if (!has_had_focus) {
            has_had_focus = true;
            $(this).val("");
        }
    });
</script>
</body>
</html>

新建普通用户

为了系统安全,新建一个普通用户,无法访问管理控制台。

在这里插入图片描述
启动项目访问该页面
在这里插入图片描述
在RabbitMQ控制发送消息

控制台向stompTest交换机发送消息,页面stomp连接到mq订阅stompTest交换器的消息从而接受到消息。

在这里插入图片描述
在这里插入图片描述
在此页面发送消息

向stompTest交换机发送消息后,又因页面stomp连接到mq订阅stompTest交换器的消息从而又接受到消息。

在这里插入图片描述

消息推送测试成功,就可以集成到项目中了。当用户完成扫码支付后,向指定的stompTest交换机发送含订单号的消息,前端判断当前页面订单号与接受的到的订单号是否一致,然后做出相应跳转。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeDevMaster

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值