微信扫码支付完整实现示例
阅读文档
在线微信支付开发文档:
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交换机发送含订单号的消息,前端判断当前页面订单号与接受的到的订单号是否一致,然后做出相应跳转。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/137072.html